From 26ec1f0183b36a203f4db794d79e3686cb4cb1b9 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 22 Sep 2023 13:14:36 +0200 Subject: [PATCH 01/43] Transform `bitcoin.ts` file to `lib/bitcoin` shared lib The `bitcoin.ts` file is a bag for all Bitcoin related code which makes it hard to read and maintain. Here we split that file into smaller ones oriented around specific Bitcoin subdomains and move them to the new `lib/bitcoin` shared library module. --- typescript/src/bitcoin.ts | 782 ------------------ typescript/src/chain.ts | 2 +- typescript/src/deposit-refund.ts | 269 +++--- typescript/src/deposit-sweep.ts | 446 ++++------ typescript/src/deposit.ts | 244 ++---- typescript/src/electrum.ts | 47 +- typescript/src/ethereum.ts | 2 +- typescript/src/index.ts | 5 +- typescript/src/lib/bitcoin/address.ts | 68 ++ typescript/src/lib/bitcoin/block.ts | 143 ++++ typescript/src/lib/bitcoin/client.ts | 94 +++ typescript/src/lib/bitcoin/csuint.ts | 41 + typescript/src/lib/bitcoin/hash.ts | 37 + typescript/src/lib/bitcoin/index.ts | 9 + typescript/src/lib/bitcoin/key.ts | 80 ++ .../bitcoin/network.ts} | 21 +- typescript/src/lib/bitcoin/proof.ts | 44 + typescript/src/lib/bitcoin/transaction.ts | 195 +++++ typescript/src/optimistic-minting.ts | 2 +- typescript/src/wallet.ts | 4 +- typescript/test/bitcoin-network.test.ts | 30 +- typescript/test/bitcoin.test.ts | 255 +----- typescript/test/data/bitcoin.ts | 4 +- typescript/test/data/deposit-refund.ts | 2 +- typescript/test/data/deposit-sweep.ts | 2 +- typescript/test/data/deposit.ts | 2 +- typescript/test/data/electrum.ts | 2 +- typescript/test/data/proof.ts | 2 +- typescript/test/data/redemption.ts | 2 +- 29 files changed, 1189 insertions(+), 1647 deletions(-) delete mode 100644 typescript/src/bitcoin.ts create mode 100644 typescript/src/lib/bitcoin/address.ts create mode 100644 typescript/src/lib/bitcoin/block.ts create mode 100644 typescript/src/lib/bitcoin/client.ts create mode 100644 typescript/src/lib/bitcoin/csuint.ts create mode 100644 typescript/src/lib/bitcoin/hash.ts create mode 100644 typescript/src/lib/bitcoin/index.ts create mode 100644 typescript/src/lib/bitcoin/key.ts rename typescript/src/{bitcoin-network.ts => lib/bitcoin/network.ts} (70%) create mode 100644 typescript/src/lib/bitcoin/proof.ts create mode 100644 typescript/src/lib/bitcoin/transaction.ts diff --git a/typescript/src/bitcoin.ts b/typescript/src/bitcoin.ts deleted file mode 100644 index 3649327a6..000000000 --- a/typescript/src/bitcoin.ts +++ /dev/null @@ -1,782 +0,0 @@ -import bufio from "bufio" -import { BigNumber, utils } from "ethers" -import { Hex } from "./hex" -import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./bitcoin-network" -import { Transaction as Tx, address, payments } from "bitcoinjs-lib" - -/** - * Represents a transaction hash (or transaction ID) as an un-prefixed hex - * string. This hash is supposed to have the same byte order as used by the - * Bitcoin block explorers which is the opposite of the byte order used - * by the Bitcoin protocol internally. That means the hash must be reversed in - * the use cases that expect the Bitcoin internal byte order. - */ -export class TransactionHash extends Hex {} - -/** - * Represents a raw transaction. - */ -export interface RawTransaction { - /** - * The full transaction payload as an un-prefixed hex string. - */ - transactionHex: string -} - -/** - * Data about a transaction. - */ -export interface Transaction { - /** - * The transaction hash (or transaction ID) as an un-prefixed hex string. - */ - transactionHash: TransactionHash - - /** - * The vector of transaction inputs. - */ - inputs: TransactionInput[] - - /** - * The vector of transaction outputs. - */ - outputs: TransactionOutput[] -} - -/** - * Data about a transaction outpoint. - */ -export interface TransactionOutpoint { - /** - * The hash of the transaction the outpoint belongs to. - */ - transactionHash: TransactionHash - - /** - * The zero-based index of the output from the specified transaction. - */ - outputIndex: number -} - -/** - * Data about a transaction input. - */ -export type TransactionInput = TransactionOutpoint & { - /** - * The scriptSig that unlocks the specified outpoint for spending. - */ - scriptSig: Hex -} - -/** - * Data about a transaction output. - */ -export interface TransactionOutput { - /** - * The 0-based index of the output. - */ - outputIndex: number - - /** - * The value of the output in satoshis. - */ - value: BigNumber - - /** - * The receiving scriptPubKey. - */ - scriptPubKey: Hex -} - -/** - * Data about an unspent transaction output. - */ -export type UnspentTransactionOutput = TransactionOutpoint & { - /** - * The unspent value in satoshis. - */ - value: BigNumber -} - -/** - * Represents data of decomposed raw transaction. - */ -export interface DecomposedRawTransaction { - /** - * Transaction version as an un-prefixed hex string. - */ - version: string - - /** - * All transaction's inputs prepended by the number of transaction inputs, - * as an un-prefixed hex string. - */ - inputs: string - - /** - * All transaction's outputs prepended by the number of transaction outputs, - * as an un-prefixed hex string. - */ - outputs: string - - /** - * Transaction locktime as an un-prefixed hex string. - */ - locktime: string -} - -/** - * Data required to perform a proof that a given transaction was included in - * the Bitcoin blockchain. - */ -export interface Proof { - /** - * The merkle proof of transaction inclusion in a block, as an un-prefixed - * hex string. - */ - merkleProof: string - - /** - * Transaction index in the block (0-indexed). - */ - txIndexInBlock: number - - /** - * Single byte-string of 80-byte block headers, lowest height first, as an - * un-prefixed hex string. - */ - bitcoinHeaders: string -} - -/** - * Information about the merkle branch to a confirmed transaction. - */ -export interface TransactionMerkleBranch { - /** - * The height of the block the transaction was confirmed in. - */ - blockHeight: number - - /** - * A list of transaction hashes the current hash is paired with, recursively, - * in order to trace up to obtain the merkle root of the including block, - * deepest pairing first. Each hash is an unprefixed hex string. - */ - merkle: string[] - - /** - * The 0-based index of the transaction's position in the block. - */ - position: number -} - -/** - * BlockHeader represents the header of a Bitcoin block. For reference, see: - * https://developer.bitcoin.org/reference/block_chain.html#block-headers. - */ -export interface BlockHeader { - /** - * The block version number that indicates which set of block validation rules - * to follow. The field is 4-byte long. - */ - version: number - - /** - * The hash of the previous block's header. The field is 32-byte long. - */ - previousBlockHeaderHash: Hex - - /** - * The hash derived from the hashes of all transactions included in this block. - * The field is 32-byte long. - */ - merkleRootHash: Hex - - /** - * The Unix epoch time when the miner started hashing the header. The field is - * 4-byte long. - */ - time: number - - /** - * Bits that determine the target threshold this block's header hash must be - * less than or equal to. The field is 4-byte long. - */ - bits: number - - /** - * An arbitrary number miners change to modify the header hash in order to - * produce a hash less than or equal to the target threshold. The field is - * 4-byte long. - */ - nonce: number -} - -/** - * Serializes a BlockHeader to the raw representation. - * @param blockHeader - block header. - * @returns Serialized block header. - */ -export function serializeBlockHeader(blockHeader: BlockHeader): Hex { - const buffer = Buffer.alloc(80) - buffer.writeUInt32LE(blockHeader.version, 0) - blockHeader.previousBlockHeaderHash.toBuffer().copy(buffer, 4) - blockHeader.merkleRootHash.toBuffer().copy(buffer, 36) - buffer.writeUInt32LE(blockHeader.time, 68) - buffer.writeUInt32LE(blockHeader.bits, 72) - buffer.writeUInt32LE(blockHeader.nonce, 76) - return Hex.from(buffer) -} - -/** - * Deserializes a block header in the raw representation to BlockHeader. - * @param rawBlockHeader - BlockHeader in the raw format. - * @returns Block header as a BlockHeader. - */ -export function deserializeBlockHeader(rawBlockHeader: Hex): BlockHeader { - const buffer = rawBlockHeader.toBuffer() - const version = buffer.readUInt32LE(0) - const previousBlockHeaderHash = buffer.slice(4, 36) - const merkleRootHash = buffer.slice(36, 68) - const time = buffer.readUInt32LE(68) - const bits = buffer.readUInt32LE(72) - const nonce = buffer.readUInt32LE(76) - - return { - version: version, - previousBlockHeaderHash: Hex.from(previousBlockHeaderHash), - merkleRootHash: Hex.from(merkleRootHash), - time: time, - bits: bits, - nonce: nonce, - } -} - -/** - * Converts a block header's bits into target. - * @param bits - bits from block header. - * @returns Target as a BigNumber. - */ -export function bitsToTarget(bits: number): BigNumber { - // A serialized 80-byte block header stores the `bits` value as a 4-byte - // little-endian hexadecimal value in a slot including bytes 73, 74, 75, and - // 76. This function's input argument is expected to be a numerical - // representation of that 4-byte value reverted to the big-endian order. - // For example, if the `bits` little-endian value in the header is - // `0xcb04041b`, it must be reverted to the big-endian form `0x1b0404cb` and - // turned to a decimal number `453248203` in order to be used as this - // function's input. - // - // The `bits` 4-byte big-endian representation is a compact value that works - // like a base-256 version of scientific notation. It encodes the target - // exponent in the first byte and the target mantissa in the last three bytes. - // Referring to the previous example, if `bits = 453248203`, the hexadecimal - // representation is `0x1b0404cb` so the exponent is `0x1b` while the mantissa - // is `0x0404cb`. - // - // To extract the exponent, we need to shift right by 3 bytes (24 bits), - // extract the last byte of the result, and subtract 3 (because of the - // mantissa length): - // - 0x1b0404cb >>> 24 = 0x0000001b - // - 0x0000001b & 0xff = 0x1b - // - 0x1b - 3 = 24 (decimal) - // - // To extract the mantissa, we just need to take the last three bytes: - // - 0x1b0404cb & 0xffffff = 0x0404cb = 263371 (decimal) - // - // The final difficulty can be computed as mantissa * 256^exponent: - // - 263371 * 256^24 = - // 1653206561150525499452195696179626311675293455763937233695932416 (decimal) - // - // Sources: - // - https://developer.bitcoin.org/reference/block_chain.html#target-nbits - // - https://wiki.bitcoinsv.io/index.php/Target - - const exponent = ((bits >>> 24) & 0xff) - 3 - const mantissa = bits & 0xffffff - - const target = BigNumber.from(mantissa).mul(BigNumber.from(256).pow(exponent)) - return target -} - -/** - * Converts difficulty target to difficulty. - * @param target - difficulty target. - * @returns Difficulty as a BigNumber. - */ -export function targetToDifficulty(target: BigNumber): BigNumber { - const DIFF1_TARGET = BigNumber.from( - "0xffff0000000000000000000000000000000000000000000000000000" - ) - return DIFF1_TARGET.div(target) -} - -/** - * Represents a Bitcoin client. - */ -export interface Client { - /** - * Gets the network supported by the server the client connected to. - * @returns Bitcoin network. - */ - getNetwork(): Promise - - /** - * Finds all unspent transaction outputs (UTXOs) for given Bitcoin address. - * @param address - Bitcoin address UTXOs should be determined for. - * @returns List of UTXOs. - */ - findAllUnspentTransactionOutputs( - address: string - ): Promise - - /** - * Gets the history of confirmed transactions for given Bitcoin address. - * Returned transactions are sorted from oldest to newest. The returned - * result does not contain unconfirmed transactions living in the mempool - * at the moment of request. - * @param address - Bitcoin address transaction history should be determined for. - * @param limit - Optional parameter that can limit the resulting list to - * a specific number of last transaction. For example, limit = 5 will - * return only the last 5 transactions for the given address. - */ - getTransactionHistory(address: string, limit?: number): Promise - - /** - * Gets the full transaction object for given transaction hash. - * @param transactionHash - Hash of the transaction. - * @returns Transaction object. - */ - getTransaction(transactionHash: TransactionHash): Promise - - /** - * Gets the raw transaction data for given transaction hash. - * @param transactionHash - Hash of the transaction. - * @returns Raw transaction. - */ - getRawTransaction(transactionHash: TransactionHash): Promise - - /** - * Gets the number of confirmations that a given transaction has accumulated - * so far. - * @param transactionHash - Hash of the transaction. - * @returns The number of confirmations. - */ - getTransactionConfirmations(transactionHash: TransactionHash): Promise - - /** - * Gets height of the latest mined block. - * @return Height of the last mined block. - */ - latestBlockHeight(): Promise - - /** - * Gets concatenated chunk of block headers built on a starting block. - * @param blockHeight - Starting block height. - * @param chainLength - Number of subsequent blocks built on the starting - * block. - * @return Concatenation of block headers in a hexadecimal format. - */ - getHeadersChain(blockHeight: number, chainLength: number): Promise - - /** - * Get Merkle branch for a given transaction. - * @param transactionHash - Hash of a transaction. - * @param blockHeight - Height of the block where transaction was confirmed. - * @return Merkle branch. - */ - getTransactionMerkle( - transactionHash: TransactionHash, - blockHeight: number - ): Promise - - /** - * Broadcasts the given transaction over the network. - * @param transaction - Transaction to broadcast. - */ - broadcast(transaction: RawTransaction): Promise -} - -/** - * Decomposes a transaction in the raw representation into version, vector of - * inputs, vector of outputs and locktime. - * @param rawTransaction - Transaction in the raw format. - * @returns Transaction data with fields represented as un-prefixed hex strings. - */ -export function decomposeRawTransaction( - rawTransaction: RawTransaction -): DecomposedRawTransaction { - const toHex = (bufferWriter: any): string => { - return bufferWriter.render().toString("hex") - } - - const getTxInputVector = (tx: Tx): string => { - const buffer = bufio.write() - buffer.writeVarint(tx.ins.length) - tx.ins.forEach((input) => { - buffer.writeHash(input.hash) - buffer.writeU32(input.index) - buffer.writeVarBytes(input.script) - buffer.writeU32(input.sequence) - }) - return toHex(buffer) - } - - const getTxOutputVector = (tx: Tx): string => { - const buffer = bufio.write() - buffer.writeVarint(tx.outs.length) - tx.outs.forEach((output) => { - buffer.writeI64(output.value) - buffer.writeVarBytes(output.script) - }) - return toHex(buffer) - } - - const getTxVersion = (tx: Tx): string => { - const buffer = bufio.write() - buffer.writeU32(tx.version) - return toHex(buffer) - } - - const getTxLocktime = (tx: Tx): string => { - const buffer = bufio.write() - buffer.writeU32(tx.locktime) - return toHex(buffer) - } - - const transaction = Tx.fromBuffer( - Buffer.from(rawTransaction.transactionHex, "hex") - ) - - return { - version: getTxVersion(transaction), - inputs: getTxInputVector(transaction), - outputs: getTxOutputVector(transaction), - locktime: getTxLocktime(transaction), - } -} - -/** - * Checks whether given public key is a compressed Bitcoin public key. - * @param publicKey - Public key that should be checked. - * @returns True if the key is a compressed Bitcoin public key, false otherwise. - */ -export function isCompressedPublicKey(publicKey: string): boolean { - // Must have 33 bytes and 02 or 03 prefix. - return ( - publicKey.length == 66 && - (publicKey.substring(0, 2) == "02" || publicKey.substring(0, 2) == "03") - ) -} - -/** - * Compresses the given uncompressed Bitcoin public key. - * @param publicKey Uncompressed 64-byte public key as an unprefixed hex string. - * @returns Compressed 33-byte public key prefixed with 02 or 03. - */ -export function compressPublicKey(publicKey: string | Hex): string { - if (typeof publicKey === "string") { - publicKey = Hex.from(publicKey) - } - - publicKey = publicKey.toString() - - // Must have 64 bytes and no prefix. - if (publicKey.length != 128) { - throw new Error( - "The public key parameter must be 64-byte. Neither 0x nor 04 prefix is allowed" - ) - } - - // The X coordinate is the first 32 bytes. - const publicKeyX = publicKey.substring(0, 64) - // The Y coordinate is the next 32 bytes. - const publicKeyY = publicKey.substring(64) - - let prefix: string - if (BigNumber.from(`0x${publicKeyY}`).mod(2).eq(0)) { - prefix = "02" - } else { - prefix = "03" - } - - return `${prefix}${publicKeyX}` -} - -/** - * Computes the HASH160 for the given text. - * @param text - Text the HASH160 is computed for. - * @returns Hash as a 20-byte un-prefixed hex string. - */ -export function computeHash160(text: string): string { - const sha256Hash = utils.sha256( - Hex.from(Buffer.from(text, "hex")).toPrefixedString() - ) - const hash160 = utils.ripemd160(sha256Hash) - - return Hex.from(hash160).toString() -} - -/** - * Computes the single SHA256 for the given text. - * @param text - Text the single SHA256 is computed for. - * @returns Hash as a 32-byte un-prefixed hex string. - */ -export function computeSha256(text: Hex): Hex { - const hash = utils.sha256(text.toPrefixedString()) - return Hex.from(hash) -} - -/** - * Computes the double SHA256 for the given text. - * @param text - Text the double SHA256 is computed for. - * @returns Hash as a 32-byte un-prefixed hex string. - */ -export function computeHash256(text: Hex): Hex { - const firstHash = utils.sha256(text.toPrefixedString()) - const secondHash = utils.sha256(firstHash) - - return Hex.from(secondHash) -} - -/** - * Converts a hash in hex string in little endian to a BigNumber. - * @param hash - Hash in hex-string format. - * @returns BigNumber representation of the hash. - */ -export function hashLEToBigNumber(hash: Hex): BigNumber { - return BigNumber.from(hash.reverse().toPrefixedString()) -} - -/** - * Encodes a public key hash into a P2PKH/P2WPKH address. - * @param publicKeyHash - public key hash that will be encoded. Must be an - * unprefixed hex string (without 0x prefix). - * @param witness - If true, a witness public key hash will be encoded and - * P2WPKH address will be returned. Returns P2PKH address otherwise - * @param bitcoinNetwork - Network the address should be encoded for. - * @returns P2PKH or P2WPKH address encoded from the given public key hash - * @throws Throws an error if network is not supported. - */ -export function encodeToBitcoinAddress( - publicKeyHash: string, - witness: boolean, - bitcoinNetwork: BitcoinNetwork -): string { - const hash = Buffer.from(publicKeyHash, "hex") - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - return witness - ? payments.p2wpkh({ hash, network }).address! - : payments.p2pkh({ hash, network }).address! -} - -/** - * Decodes P2PKH or P2WPKH address into a public key hash. Throws if the - * provided address is not PKH-based. - * @param bitcoinAddress - P2PKH or P2WPKH address that will be decoded. - * @param bitcoinNetwork - Bitcoin network. - * @returns Public key hash decoded from the address. This will be an unprefixed - * hex string (without 0x prefix). - */ -export function decodeBitcoinAddress( - bitcoinAddress: string, - bitcoinNetwork: BitcoinNetwork -): string { - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - - try { - // Try extracting hash from P2PKH address. - const hash = payments.p2pkh({ address: bitcoinAddress, network }).hash! - return hash.toString("hex") - } catch (err) {} - - try { - // Try extracting hash from P2WPKH address. - const hash = payments.p2wpkh({ address: bitcoinAddress, network }).hash! - return hash.toString("hex") - } catch (err) {} - - throw new Error("Address must be P2PKH or P2WPKH valid for given network") -} - -/** - * Checks if given public key hash has proper length (20-byte) - * @param publicKeyHash - text that will be checked for the correct length - * @returns true if the given string is 20-byte long, false otherwise - */ -export function isPublicKeyHashLength(publicKeyHash: string): boolean { - return publicKeyHash.length === 40 -} - -/** - * Converts Bitcoin specific locktime value to a number. The number represents - * either a block height or an Unix timestamp depending on the value. - * - * If the number is less than 500 000 000 it is a block height. - * If the number is greater or equal 500 000 000 it is a Unix timestamp. - * - * @see {@link https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number Documentation} - * - * @param locktimeLE A 4-byte little-endian locktime as an un-prefixed - * hex string {@link: Deposit#refundLocktime}. - * @returns UNIX timestamp in seconds. - */ -export function locktimeToNumber(locktimeLE: Buffer | string): number { - const locktimeBE: Buffer = Hex.from(locktimeLE).reverse().toBuffer() - return BigNumber.from(locktimeBE).toNumber() -} - -/** - * Creates the output script from the BTC address. - * @param bitcoinAddress Bitcoin address. - * @param bitcoinNetwork Bitcoin network. - * @returns The un-prefixed and not prepended with length output script. - */ -export function createOutputScriptFromAddress( - bitcoinAddress: string, - bitcoinNetwork: BitcoinNetwork -): Hex { - return Hex.from( - address.toOutputScript( - bitcoinAddress, - toBitcoinJsLibNetwork(bitcoinNetwork) - ) - ) -} - -/** - * Creates the Bitcoin address from the output script. - * @param script The unprefixed and not prepended with length output script. - * @param bitcoinNetwork Bitcoin network. - * @returns The Bitcoin address. - */ -export function createAddressFromOutputScript( - script: Hex, - bitcoinNetwork: BitcoinNetwork = BitcoinNetwork.Mainnet -): string { - return address.fromOutputScript( - script.toBuffer(), - toBitcoinJsLibNetwork(bitcoinNetwork) - ) -} - -/** - * Creates the Bitcoin address from the public key. Supports SegWit (P2WPKH) and - * Legacy (P2PKH) formats. - * @param publicKey - Public key used to derive the Bitcoin address. - * @param bitcoinNetwork - Target Bitcoin network. - * @param witness - Flag to determine address format: true for SegWit (P2WPKH) - * and false for Legacy (P2PKH). Default is true. - * @returns The derived Bitcoin address. - */ -export function createAddressFromPublicKey( - publicKey: Hex, - bitcoinNetwork: BitcoinNetwork, - witness: boolean = true -): string { - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - - if (witness) { - // P2WPKH (SegWit) - return payments.p2wpkh({ pubkey: publicKey.toBuffer(), network }).address! - } else { - // P2PKH (Legacy) - return payments.p2pkh({ pubkey: publicKey.toBuffer(), network }).address! - } -} - -/** - * Reads the leading compact size uint from the provided variable length data. - * - * WARNING: CURRENTLY, THIS FUNCTION SUPPORTS ONLY 1-BYTE COMPACT SIZE UINTS - * AND WILL THROW ON COMPACT SIZE UINTS OF DIFFERENT BYTE LENGTH. - * - * @param varLenData Variable length data preceded by a compact size uint. - * @returns An object holding the value of the compact size uint along with the - * compact size uint byte length. - */ -export function readCompactSizeUint(varLenData: Hex): { - value: number - byteLength: number -} { - // The varLenData is prefixed with the compact size uint. According to the docs - // (https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers) - // a compact size uint can be 1, 3, 5 or 9 bytes. To determine the exact length, - // we need to look at the discriminant byte which is always the first byte of - // the compact size uint. - const discriminant = varLenData.toString().slice(0, 2) - - switch (discriminant) { - case "ff": - case "fe": - case "fd": { - throw new Error( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" - ) - } - default: { - // The discriminant tells the compact size uint is 1 byte. That means - // the discriminant represent the value itself. - return { - value: parseInt(discriminant, 16), - byteLength: 1, - } - } - } -} - -/** - * Checks if the provided script comes from a P2PKH input. - * @param script The script to be checked. - * @returns True if the script is P2PKH, false otherwise. - */ -export function isP2PKHScript(script: Buffer): boolean { - try { - payments.p2pkh({ output: script }) - return true - } catch (err) { - return false - } -} - -/** - * Checks if the provided script comes from a P2WPKH input. - * @param script The script to be checked. - * @returns True if the script is P2WPKH, false otherwise. - */ -export function isP2WPKHScript(script: Buffer): boolean { - try { - payments.p2wpkh({ output: script }) - return true - } catch (err) { - return false - } -} - -/** - * Checks if the provided script comes from a P2SH input. - * @param script The script to be checked. - * @returns True if the script is P2SH, false otherwise. - */ -export function isP2SHScript(script: Buffer): boolean { - try { - payments.p2sh({ output: script }) - return true - } catch (err) { - return false - } -} - -/** - * Checks if the provided script comes from a P2PKH input. - * @param script The script to be checked. - * @returns True if the script is P2WSH, false otherwise. - */ -export function isP2WSHScript(script: Buffer): boolean { - try { - payments.p2wsh({ output: script }) - return true - } catch (err) { - return false - } -} diff --git a/typescript/src/chain.ts b/typescript/src/chain.ts index 3b4686dc5..0b840dabe 100644 --- a/typescript/src/chain.ts +++ b/typescript/src/chain.ts @@ -4,7 +4,7 @@ import { UnspentTransactionOutput, DecomposedRawTransaction, TransactionHash, -} from "./bitcoin" +} from "./lib/bitcoin" import { DepositRevealedEvent, DepositScriptParameters, diff --git a/typescript/src/deposit-refund.ts b/typescript/src/deposit-refund.ts index 2f6b9ee88..ec34038a1 100644 --- a/typescript/src/deposit-refund.ts +++ b/typescript/src/deposit-refund.ts @@ -1,24 +1,19 @@ +import bcoin from "bcoin" import { BigNumber } from "ethers" -import { Transaction, Stack, Signer, script } from "bitcoinjs-lib" import { + createKeyRing, RawTransaction, Client as BitcoinClient, TransactionHash, UnspentTransactionOutput, computeHash160, isCompressedPublicKey, - createOutputScriptFromAddress, - isP2SHScript, - isP2WSHScript, -} from "./bitcoin" +} from "./lib/bitcoin" import { assembleDepositScript, Deposit, validateDepositScriptParameters, } from "./deposit" -import { ECPairFactory } from "ecpair" -import * as tinysecp from "tiny-secp256k1" -import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./bitcoin-network" /** * Submits a deposit refund by creating and broadcasting a Bitcoin P2(W)PKH @@ -57,11 +52,8 @@ export async function submitDepositRefundTransaction( transactionHex: utxoRawTransaction.transactionHex, } - const bitcoinNetwork = await bitcoinClient.getNetwork() - const { transactionHash, rawTransaction } = await assembleDepositRefundTransaction( - bitcoinNetwork, fee, utxoWithRaw, deposit, @@ -79,7 +71,6 @@ export async function submitDepositRefundTransaction( /** * Assembles a Bitcoin P2(W)PKH deposit refund transaction. - * @param bitcoinNetwork - The target Bitcoin network. * @param fee - the value that will be subtracted from the deposit UTXO being * refunded and used as the transaction fee. * @param utxo - UTXO that was created during depositing that needs be refunded. @@ -94,7 +85,6 @@ export async function submitDepositRefundTransaction( * - the refund transaction in the raw format. */ export async function assembleDepositRefundTransaction( - bitcoinNetwork: BitcoinNetwork, fee: BigNumber, utxo: UnspentTransactionOutput & RawTransaction, deposit: Deposit, @@ -106,97 +96,101 @@ export async function assembleDepositRefundTransaction( }> { validateInputParameters(deposit, utxo) - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - // eslint-disable-next-line new-cap - const refunderKeyPair = ECPairFactory(tinysecp).fromWIF( - refunderPrivateKey, - network - ) + const refunderKeyRing = createKeyRing(refunderPrivateKey) - const outputValue = utxo.value.sub(fee) + const transaction = new bcoin.MTX() - const transaction = new Transaction() + transaction.addOutput({ + script: bcoin.Script.fromAddress(refunderAddress), + value: utxo.value.toNumber(), + }) - transaction.addInput( - utxo.transactionHash.reverse().toBuffer(), - utxo.outputIndex + const inputCoin = bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), + utxo.outputIndex, + -1 ) - const outputScript = createOutputScriptFromAddress( - refunderAddress, - bitcoinNetwork - ) - transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber()) + await transaction.fund([inputCoin], { + changeAddress: refunderAddress, + hardFee: fee.toNumber(), + subtractFee: true, + }) + + if (transaction.outputs.length != 1) { + throw new Error("Deposit refund transaction must have only one output") + } // In order to be able to spend the UTXO being refunded the transaction's // locktime must be set to a value equal to or higher than the refund locktime. // Additionally, the input's sequence must be set to a value different than // `0xffffffff`. These requirements are the result of BIP-65. transaction.locktime = locktimeToUnixTimestamp(deposit.refundLocktime) - transaction.ins[0].sequence = 0xfffffffe + transaction.inputs[0].sequence = 0xfffffffe // Sign the input - const previousOutput = Transaction.fromHex(utxo.transactionHex).outs[ - utxo.outputIndex - ] - const previousOutputValue = previousOutput.value - const previousOutputScript = previousOutput.script - - if (isP2SHScript(previousOutputScript)) { - // P2SH deposit UTXO - await signP2SHDepositInput( - transaction, - 0, - deposit, - previousOutputValue, - refunderKeyPair - ) - } else if (isP2WSHScript(previousOutputScript)) { - // P2WSH deposit UTXO - await signP2WSHDepositInput( - transaction, - 0, - deposit, - previousOutputValue, - refunderKeyPair - ) + const previousOutpoint = transaction.inputs[0].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + const previousScript = previousOutput.script + + if (previousScript.isScripthash()) { + // P2SH UTXO deposit input + await signP2SHDepositInput(transaction, 0, deposit, refunderKeyRing) + } else if (previousScript.isWitnessScripthash()) { + // P2WSH UTXO deposit input + await signP2WSHDepositInput(transaction, 0, deposit, refunderKeyRing) } else { throw new Error("Unsupported UTXO script type") } - const transactionHash = TransactionHash.from(transaction.getId()) + // Verify the transaction by executing its input scripts. + const tx = transaction.toTX() + if (!tx.verify(transaction.view)) { + throw new Error("Transaction verification failure") + } + + const transactionHash = TransactionHash.from(transaction.txid()) return { transactionHash, rawTransaction: { - transactionHex: transaction.toHex(), + transactionHex: transaction.toRaw().toString("hex"), }, } } /** - * Assembles the deposit script based on the given deposit details. Performs - * validations on values and key formats. - * @param deposit - The deposit details. - * @param previousOutputValue - Value from the previous transaction output. - * @param refunderKeyPair - Signer object containing the refunder's key pair. - * @returns A Promise resolving to the assembled deposit script as a Buffer. - * @throws Error if there are discrepancies in values or key formats. + * Creates data needed to sign a deposit input to be refunded. + * @param transaction - Mutable transaction containing the input to be refunded. + * @param inputIndex - Index that points to the input. + * @param deposit - Data of the deposit to be refunded. + * @param refunderKeyRing - Key ring created using the refunder's private key. + * @returns Data needed to sign the input. */ -async function prepareDepositScript( +async function prepareInputSignData( + transaction: any, + inputIndex: number, deposit: Deposit, - previousOutputValue: number, - refunderKeyPair: Signer -): Promise { - if (previousOutputValue != deposit.amount.toNumber()) { - throw new Error("Mismatch between amount in deposit and deposit tx") - } + refunderKeyRing: any +): Promise<{ + refunderPublicKey: string + depositScript: any + previousOutputValue: number +}> { + const previousOutpoint = transaction.inputs[inputIndex].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) - const refunderPublicKey = refunderKeyPair.publicKey.toString("hex") + if (previousOutput.value != deposit.amount.toNumber()) { + throw new Error("Mismatch between amount in deposit and deposit refund tx") + } - if (computeHash160(refunderPublicKey) != deposit.refundPublicKeyHash) { + const refunderPublicKey = refunderKeyRing.getPublicKey("hex") + if ( + computeHash160(refunderKeyRing.getPublicKey("hex")) != + deposit.refundPublicKeyHash + ) { throw new Error( - "Refund public key does not correspond to wallet private key" + "Refund public key does not correspond to the refunder private key" ) } @@ -207,101 +201,98 @@ async function prepareDepositScript( // eslint-disable-next-line no-unused-vars const { amount, vault, ...depositScriptParameters } = deposit - const depositScript = Buffer.from( - await assembleDepositScript(depositScriptParameters), - "hex" + const depositScript = bcoin.Script.fromRaw( + Buffer.from(await assembleDepositScript(depositScriptParameters), "hex") ) - return depositScript + return { + refunderPublicKey: refunderPublicKey, + depositScript: depositScript, + previousOutputValue: previousOutput.value, + } } /** - * Signs a P2SH deposit transaction input and sets the `scriptSig`. - * @param transaction - The transaction containing the input to be signed. - * @param inputIndex - Index pointing to the input within the transaction. - * @param deposit - Details of the deposit transaction. - * @param previousOutputValue - The value from the previous transaction output. - * @param refunderKeyPair - A Signer object with the refunder's public and private - * key pair. - * @returns An empty promise upon successful signing. + * Creates and sets `scriptSig` for the transaction input at the given index by + * combining signature, refunder's public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param deposit - Data of the deposit. + * @param refunderKeyRing - Key ring created using the refunder's private key. + * @returns Empty return. */ async function signP2SHDepositInput( - transaction: Transaction, + transaction: any, inputIndex: number, deposit: Deposit, - previousOutputValue: number, - refunderKeyPair: Signer + refunderKeyRing: any ) { - const depositScript = await prepareDepositScript( - deposit, - previousOutputValue, - refunderKeyPair - ) - - const sigHashType = Transaction.SIGHASH_ALL + const { refunderPublicKey, depositScript, previousOutputValue } = + await prepareInputSignData( + transaction, + inputIndex, + deposit, + refunderKeyRing + ) - const sigHash = transaction.hashForSignature( + const signature: Buffer = transaction.signature( inputIndex, depositScript, - sigHashType - ) - - const signature = script.signature.encode( - refunderKeyPair.sign(sigHash), - sigHashType + previousOutputValue, + refunderKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 0 // legacy sighash version ) - - const scriptSig: Stack = [] - scriptSig.push(signature) - scriptSig.push(refunderKeyPair.publicKey) - scriptSig.push(depositScript) - - transaction.ins[inputIndex].script = script.compile(scriptSig) + const scriptSig = new bcoin.Script() + scriptSig.clear() + scriptSig.pushData(signature) + scriptSig.pushData(Buffer.from(refunderPublicKey, "hex")) + scriptSig.pushData(depositScript.toRaw()) + scriptSig.compile() + + transaction.inputs[inputIndex].script = scriptSig } /** - * Signs a P2WSH deposit transaction input and sets the witness script. - * @param transaction - The transaction containing the input to be signed. - * @param inputIndex - Index pointing to the input within the transaction. - * @param deposit - Details of the deposit transaction. - * @param previousOutputValue - The value from the previous transaction output. - * @param refunderKeyPair - A Signer object with the refunder's public and private - * key pair. - * @returns An empty promise upon successful signing. + * Creates and sets witness script for the transaction input at the given index + * by combining signature, refunder public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param deposit - Data of the deposit. + * @param refunderKeyRing - Key ring created using the refunder's private key. + * @returns Empty return. */ async function signP2WSHDepositInput( - transaction: Transaction, + transaction: any, inputIndex: number, deposit: Deposit, - previousOutputValue: number, - refunderKeyPair: Signer + refunderKeyRing: any ) { - const depositScript = await prepareDepositScript( - deposit, - previousOutputValue, - refunderKeyPair - ) - - const sigHashType = Transaction.SIGHASH_ALL + const { refunderPublicKey, depositScript, previousOutputValue } = + await prepareInputSignData( + transaction, + inputIndex, + deposit, + refunderKeyRing + ) - const sigHash = transaction.hashForWitnessV0( + const signature: Buffer = transaction.signature( inputIndex, depositScript, previousOutputValue, - sigHashType - ) - - const signature = script.signature.encode( - refunderKeyPair.sign(sigHash), - sigHashType + refunderKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 1 // segwit sighash version ) - const witness: Buffer[] = [] - witness.push(signature) - witness.push(refunderKeyPair.publicKey) - witness.push(depositScript) + const witness = new bcoin.Witness() + witness.clear() + witness.pushData(signature) + witness.pushData(Buffer.from(refunderPublicKey, "hex")) + witness.pushData(depositScript.toRaw()) + witness.compile() - transaction.ins[inputIndex].witness = witness + transaction.inputs[inputIndex].witness = witness } /** diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 03cfc5e13..9a1e00f7c 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -1,34 +1,18 @@ -import { - Transaction, - TxOutput, - Stack, - Signer, - payments, - script, -} from "bitcoinjs-lib" +import bcoin from "bcoin" import { BigNumber } from "ethers" -import { Hex } from "./hex" import { RawTransaction, UnspentTransactionOutput, Client as BitcoinClient, decomposeRawTransaction, isCompressedPublicKey, - createAddressFromPublicKey, + createKeyRing, TransactionHash, computeHash160, - isP2PKHScript, - isP2WPKHScript, - isP2SHScript, - isP2WSHScript, - createOutputScriptFromAddress, -} from "./bitcoin" +} from "./lib/bitcoin" import { assembleDepositScript, Deposit } from "./deposit" import { Bridge, Identifier } from "./chain" import { assembleTransactionProof } from "./proof" -import { ECPairFactory } from "ecpair" -import * as tinysecp from "tiny-secp256k1" -import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./bitcoin-network" /** * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and @@ -88,11 +72,8 @@ export async function submitDepositSweepTransaction( } } - const bitcoinNetwork = await bitcoinClient.getNetwork() - const { transactionHash, newMainUtxo, rawTransaction } = await assembleDepositSweepTransaction( - bitcoinNetwork, fee, walletPrivateKey, witness, @@ -110,28 +91,26 @@ export async function submitDepositSweepTransaction( } /** - * Constructs a Bitcoin deposit sweep transaction using provided UTXOs. + * Assembles a Bitcoin P2WPKH deposit sweep transaction. * @dev The caller is responsible for ensuring the provided UTXOs are correctly * formed, can be spent by the wallet and their combined value is greater * then the fee. - * @param bitcoinNetwork - The target Bitcoin network. - * @param fee - Transaction fee to be subtracted from the sum of the UTXOs' - * values. + * @param fee - the value that should be subtracted from the sum of the UTXOs + * values and used as the transaction fee. * @param walletPrivateKey - Bitcoin private key of the wallet in WIF format. - * @param witness - Determines the type of the new main UTXO output: P2WPKH if - * `true`, P2PKH if `false`. + * @param witness - The parameter used to decide about the type of the new main + * UTXO output. P2WPKH if `true`, P2PKH if `false`. * @param utxos - UTXOs from new deposit transactions. Must be P2(W)SH. - * @param deposits - Deposit data corresponding to each UTXO. The number of - * UTXOs and deposits must match. - * @param mainUtxo - The wallet's main UTXO (optional), which is a P2(W)PKH UTXO - * from a previous transaction. - * @returns An object containing the sweep transaction hash, new wallet's main - * UTXO, and the raw deposit sweep transaction representation. - * @throws Error if the provided UTXOs and deposits mismatch or if an unsupported - * UTXO script type is encountered. + * @param deposits - Array of deposits. Each element corresponds to UTXO. + * The number of UTXOs and deposit elements must equal. + * @param mainUtxo - main UTXO of the wallet, which is a P2WKH UTXO resulting + * from the previous wallet transaction (optional). + * @returns The outcome consisting of: + * - the sweep transaction hash, + * - the new wallet's main UTXO produced by this transaction. + * - the sweep transaction in the raw format */ export async function assembleDepositSweepTransaction( - bitcoinNetwork: BitcoinNetwork, fee: BigNumber, walletPrivateKey: string, witness: boolean, @@ -151,291 +130,238 @@ export async function assembleDepositSweepTransaction( throw new Error("Number of UTXOs must equal the number of deposit elements") } - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - // eslint-disable-next-line new-cap - const walletKeyPair = ECPairFactory(tinysecp).fromWIF( - walletPrivateKey, - network - ) - const walletAddress = createAddressFromPublicKey( - Hex.from(walletKeyPair.publicKey), - bitcoinNetwork, - witness - ) + const walletKeyRing = createKeyRing(walletPrivateKey, witness) + const walletAddress = walletKeyRing.getAddress("string") - const transaction = new Transaction() + const inputCoins = [] + let totalInputValue = BigNumber.from(0) - let outputValue = BigNumber.from(0) if (mainUtxo) { - transaction.addInput( - mainUtxo.transactionHash.reverse().toBuffer(), - mainUtxo.outputIndex + inputCoins.push( + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), + mainUtxo.outputIndex, + -1 + ) ) - outputValue = outputValue.add(mainUtxo.value) + totalInputValue = totalInputValue.add(mainUtxo.value) } + for (const utxo of utxos) { - transaction.addInput( - utxo.transactionHash.reverse().toBuffer(), - utxo.outputIndex + inputCoins.push( + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), + utxo.outputIndex, + -1 + ) ) - outputValue = outputValue.add(utxo.value) + totalInputValue = totalInputValue.add(utxo.value) } - outputValue = outputValue.sub(fee) - const outputScript = createOutputScriptFromAddress( - walletAddress, - bitcoinNetwork - ) - transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber()) + const transaction = new bcoin.MTX() - // Sign the main UTXO input if there is main UTXO. - if (mainUtxo) { - const inputIndex = 0 // Main UTXO is the first input. - const previousOutput = Transaction.fromHex(mainUtxo.transactionHex).outs[ - mainUtxo.outputIndex - ] - - await signMainUtxoInput( - transaction, - inputIndex, - previousOutput, - walletKeyPair - ) - } + transaction.addOutput({ + script: bcoin.Script.fromAddress(walletAddress), + value: totalInputValue.toNumber(), + }) - // Sign the deposit inputs. - for (let depositIndex = 0; depositIndex < deposits.length; depositIndex++) { - // If there is a main UTXO index, we must adjust input index as the first - // input is the main UTXO input. - const inputIndex = mainUtxo ? depositIndex + 1 : depositIndex + await transaction.fund(inputCoins, { + changeAddress: walletAddress, + hardFee: fee.toNumber(), + subtractFee: true, + }) + + if (transaction.outputs.length != 1) { + throw new Error("Deposit sweep transaction must have only one output") + } - const utxo = utxos[depositIndex] - const previousOutput = Transaction.fromHex(utxo.transactionHex).outs[ - utxo.outputIndex - ] - const previousOutputValue = previousOutput.value - const previousOutputScript = previousOutput.script + // UTXOs must be mapped to deposits, as `fund` may arrange inputs in any + // order + const utxosWithDeposits: (UnspentTransactionOutput & + RawTransaction & + Deposit)[] = utxos.map((utxo, index) => ({ + ...utxo, + ...deposits[index], + })) + + for (let i = 0; i < transaction.inputs.length; i++) { + const previousOutpoint = transaction.inputs[i].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + const previousScript = previousOutput.script + + // P2(W)PKH (main UTXO) + if (previousScript.isPubkeyhash() || previousScript.isWitnessPubkeyhash()) { + await signMainUtxoInput(transaction, i, walletKeyRing) + continue + } - const deposit = deposits[depositIndex] + const utxoWithDeposit = utxosWithDeposits.find( + (u) => + u.transactionHash.toString() === previousOutpoint.txid() && + u.outputIndex == previousOutpoint.index + ) + if (!utxoWithDeposit) { + throw new Error("Unknown input") + } - if (isP2SHScript(previousOutputScript)) { + if (previousScript.isScripthash()) { // P2SH (deposit UTXO) - await signP2SHDepositInput( - transaction, - inputIndex, - deposit, - previousOutputValue, - walletKeyPair - ) - } else if (isP2WSHScript(previousOutputScript)) { + await signP2SHDepositInput(transaction, i, utxoWithDeposit, walletKeyRing) + } else if (previousScript.isWitnessScripthash()) { // P2WSH (deposit UTXO) await signP2WSHDepositInput( transaction, - inputIndex, - deposit, - previousOutputValue, - walletKeyPair + i, + utxoWithDeposit, + walletKeyRing ) } else { throw new Error("Unsupported UTXO script type") } } - const transactionHash = TransactionHash.from(transaction.getId()) + const transactionHash = TransactionHash.from(transaction.txid()) return { transactionHash, newMainUtxo: { transactionHash, outputIndex: 0, // There is only one output. - value: BigNumber.from(transaction.outs[0].value), + value: BigNumber.from(transaction.outputs[0].value), }, rawTransaction: { - transactionHex: transaction.toHex(), + transactionHex: transaction.toRaw().toString("hex"), }, } } /** - * Signs the main UTXO transaction input and sets the appropriate script or - * witness data. - * @param transaction - The transaction containing the input to be signed. - * @param inputIndex - Index pointing to the input within the transaction. - * @param previousOutput - The previous output for the main UTXO input. - * @param walletKeyPair - A Signer object with the wallet's public and private - * key pair. - * @param bitcoinNetwork - The Bitcoin network type. - * @returns An empty promise upon successful signing. - * @throws Error if the UTXO doesn't belong to the wallet, or if the script - * format is invalid or unknown. + * Creates script for the transaction input at the given index and signs the + * input. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Empty promise. */ async function signMainUtxoInput( - transaction: Transaction, + transaction: any, inputIndex: number, - previousOutput: TxOutput, - walletKeyPair: Signer + walletKeyRing: any ) { - if ( - !canSpendOutput(Hex.from(walletKeyPair.publicKey), previousOutput.script) - ) { + const previousOutpoint = transaction.inputs[inputIndex].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + if (!walletKeyRing.ownOutput(previousOutput)) { throw new Error("UTXO does not belong to the wallet") } - - const sigHashType = Transaction.SIGHASH_ALL - - if (isP2PKHScript(previousOutput.script)) { - // P2PKH - const sigHash = transaction.hashForSignature( - inputIndex, - previousOutput.script, - sigHashType - ) - - const signature = script.signature.encode( - walletKeyPair.sign(sigHash), - sigHashType - ) - - const scriptSig = payments.p2pkh({ - signature: signature, - pubkey: walletKeyPair.publicKey, - }).input! - - transaction.ins[inputIndex].script = scriptSig - } else if (isP2WPKHScript(previousOutput.script)) { - // P2WPKH - const publicKeyHash = payments.p2wpkh({ output: previousOutput.script }) - .hash! - const p2pkhScript = payments.p2pkh({ hash: publicKeyHash }).output! - - const sigHash = transaction.hashForWitnessV0( - inputIndex, - p2pkhScript, - previousOutput.value, - sigHashType - ) - - const signature = script.signature.encode( - walletKeyPair.sign(sigHash), - sigHashType - ) - - transaction.ins[inputIndex].witness = [signature, walletKeyPair.publicKey] - } else { - throw new Error("Unknown type of main UTXO") - } + // Build script and set it as input's witness + transaction.scriptInput(inputIndex, previousOutput, walletKeyRing) + // Build signature and add it in front of script in input's witness + transaction.signInput(inputIndex, previousOutput, walletKeyRing) } /** - * Signs a P2SH deposit transaction input and sets the `scriptSig`. - * @param transaction - The transaction containing the input to be signed. - * @param inputIndex - Index pointing to the input within the transaction. - * @param deposit - Details of the deposit transaction. - * @param previousOutputValue - The value from the previous transaction output. - * @param walletKeyPair - A Signer object with the wallet's public and private - * key pair. - * @returns An empty promise upon successful signing. + * Creates and sets `scriptSig` for the transaction input at the given index by + * combining signature, wallet public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param deposit - Data of the deposit. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Empty promise. */ async function signP2SHDepositInput( - transaction: Transaction, + transaction: any, inputIndex: number, deposit: Deposit, - previousOutputValue: number, - walletKeyPair: Signer -) { - const depositScript = await prepareDepositScript( - deposit, - previousOutputValue, - walletKeyPair - ) - - const sigHashType = Transaction.SIGHASH_ALL + walletKeyRing: any +): Promise { + const { walletPublicKey, depositScript, previousOutputValue } = + await prepareInputSignData(transaction, inputIndex, deposit, walletKeyRing) - const sigHash = transaction.hashForSignature( + const signature: Buffer = transaction.signature( inputIndex, depositScript, - sigHashType - ) - - const signature = script.signature.encode( - walletKeyPair.sign(sigHash), - sigHashType + previousOutputValue, + walletKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 0 // legacy sighash version ) - - const scriptSig: Stack = [] - scriptSig.push(signature) - scriptSig.push(walletKeyPair.publicKey) - scriptSig.push(depositScript) - - transaction.ins[inputIndex].script = script.compile(scriptSig) + const scriptSig = new bcoin.Script() + scriptSig.clear() + scriptSig.pushData(signature) + scriptSig.pushData(Buffer.from(walletPublicKey, "hex")) + scriptSig.pushData(depositScript.toRaw()) + scriptSig.compile() + + transaction.inputs[inputIndex].script = scriptSig } /** - * Signs a P2WSH deposit transaction input and sets the witness script. - * @param transaction - The transaction containing the input to be signed. - * @param inputIndex - Index pointing to the input within the transaction. - * @param deposit - Details of the deposit transaction. - * @param previousOutputValue - The value from the previous transaction output. - * @param walletKeyPair - A Signer object with the wallet's public and private - * key pair. - * @returns An empty promise upon successful signing. + * Creates and sets witness script for the transaction input at the given index + * by combining signature, wallet public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param deposit - Data of the deposit. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Empty promise. */ async function signP2WSHDepositInput( - transaction: Transaction, + transaction: any, inputIndex: number, deposit: Deposit, - previousOutputValue: number, - walletKeyPair: Signer -) { - const depositScript = await prepareDepositScript( - deposit, - previousOutputValue, - walletKeyPair - ) - - const sigHashType = Transaction.SIGHASH_ALL + walletKeyRing: any +): Promise { + const { walletPublicKey, depositScript, previousOutputValue } = + await prepareInputSignData(transaction, inputIndex, deposit, walletKeyRing) - const sigHash = transaction.hashForWitnessV0( + const signature: Buffer = transaction.signature( inputIndex, depositScript, previousOutputValue, - sigHashType - ) - - const signature = script.signature.encode( - walletKeyPair.sign(sigHash), - sigHashType + walletKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 1 // segwit sighash version ) - const witness: Buffer[] = [] - witness.push(signature) - witness.push(walletKeyPair.publicKey) - witness.push(depositScript) + const witness = new bcoin.Witness() + witness.clear() + witness.pushData(signature) + witness.pushData(Buffer.from(walletPublicKey, "hex")) + witness.pushData(depositScript.toRaw()) + witness.compile() - transaction.ins[inputIndex].witness = witness + transaction.inputs[inputIndex].witness = witness } /** - * Assembles the deposit script based on the given deposit details. Performs - * validations on values and key formats. - * @param deposit - The deposit details. - * @param previousOutputValue - Value from the previous transaction output. - * @param walletKeyPair - Signer object containing the wallet's key pair. - * @returns A Promise resolving to the assembled deposit script as a Buffer. - * @throws Error if there are discrepancies in values or key formats. + * Creates data needed to sign a deposit input. + * @param transaction - Mutable transaction containing the input. + * @param inputIndex - Index that points to the input. + * @param deposit - Data of the deposit. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Data needed to sign the input. */ -async function prepareDepositScript( +async function prepareInputSignData( + transaction: any, + inputIndex: number, deposit: Deposit, - previousOutputValue: number, - walletKeyPair: Signer -): Promise { - if (previousOutputValue != deposit.amount.toNumber()) { + walletKeyRing: any +): Promise<{ + walletPublicKey: string + depositScript: any + previousOutputValue: number +}> { + const previousOutpoint = transaction.inputs[inputIndex].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + + if (previousOutput.value != deposit.amount.toNumber()) { throw new Error("Mismatch between amount in deposit and deposit tx") } - const walletPublicKey = walletKeyPair.publicKey.toString("hex") - - if (computeHash160(walletPublicKey) != deposit.walletPublicKeyHash) { + const walletPublicKey = walletKeyRing.getPublicKey("hex") + if ( + computeHash160(walletKeyRing.getPublicKey("hex")) != + deposit.walletPublicKeyHash + ) { throw new Error( "Wallet public key does not correspond to wallet private key" ) @@ -448,12 +374,15 @@ async function prepareDepositScript( // eslint-disable-next-line no-unused-vars const { amount, vault, ...depositScriptParameters } = deposit - const depositScript = Buffer.from( - await assembleDepositScript(depositScriptParameters), - "hex" + const depositScript = bcoin.Script.fromRaw( + Buffer.from(await assembleDepositScript(depositScriptParameters), "hex") ) - return depositScript + return { + walletPublicKey, + depositScript: depositScript, + previousOutputValue: previousOutput.value, + } } /** @@ -491,20 +420,3 @@ export async function submitDepositSweepProof( vault ) } - -/** - * Determines if a UTXO's output script can be spent using the provided public - * key. - * @param publicKey - Public key used to derive the corresponding P2PKH and - * P2WPKH output scripts. - * @param outputScript - The output script of the UTXO in question. - * @returns True if the provided output script matches the P2PKH or P2WPKH - * output scripts derived from the given public key. False otherwise. - */ -function canSpendOutput(publicKey: Hex, outputScript: Buffer): boolean { - const pubkeyBuffer = publicKey.toBuffer() - const p2pkhOutput = payments.p2pkh({ pubkey: pubkeyBuffer }).output! - const p2wpkhOutput = payments.p2wpkh({ pubkey: pubkeyBuffer }).output! - - return outputScript.equals(p2pkhOutput) || outputScript.equals(p2wpkhOutput) -} diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index f897dadbc..1deb84609 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -1,29 +1,20 @@ +import bcoin from "bcoin" import { BigNumber } from "ethers" -import { - Psbt, - Stack, - Transaction, - payments, - script, - opcodes, -} from "bitcoinjs-lib" import { Client as BitcoinClient, - createAddressFromPublicKey, + BitcoinNetwork, + toBcoinNetwork, decomposeRawTransaction, + createKeyRing, RawTransaction, UnspentTransactionOutput, TransactionHash, isPublicKeyHashLength, - computeSha256, - computeHash160, - isP2WPKHScript, -} from "./bitcoin" -import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./bitcoin-network" +} from "./lib/bitcoin" import { Bridge, Event, Identifier } from "./chain" import { Hex } from "./hex" -import { ECPairFactory } from "ecpair" -import * as tinysecp from "tiny-secp256k1" + +const { opcodes } = bcoin.script.common // TODO: Replace all properties that are expected to be un-prefixed hexadecimal // strings with a Hex type. @@ -129,34 +120,28 @@ export type DepositRevealedEvent = Deposit & { * @param bitcoinClient - Bitcoin client used to interact with the network. * @param witness - If true, a witness (P2WSH) transaction will be created. * Otherwise, a legacy P2SH transaction will be made. - * @param inputUtxos - UTXOs to be used for funding the deposit transaction. So - * far only P2WPKH UTXO inputs are supported. - * @param fee - the value that should be subtracted from the sum of the UTXOs - * values and used as the transaction fee. * @returns The outcome consisting of: * - the deposit transaction hash, * - the deposit UTXO produced by this transaction. - * @dev UTXOs are selected for transaction funding based on their types. UTXOs - * with unsupported types are skipped. The selection process stops once - * the sum of the chosen UTXOs meets the required funding amount. - * Be aware that the function will attempt to broadcast the transaction, - * although successful broadcast is not guaranteed. - * @throws {Error} When the sum of the selected UTXOs is insufficient to cover - * the deposit amount and transaction fee. */ export async function submitDepositTransaction( deposit: Deposit, depositorPrivateKey: string, bitcoinClient: BitcoinClient, - witness: boolean, - inputUtxos: UnspentTransactionOutput[], - fee: BigNumber + witness: boolean ): Promise<{ transactionHash: TransactionHash depositUtxo: UnspentTransactionOutput }> { + const depositorKeyRing = createKeyRing(depositorPrivateKey) + const depositorAddress = depositorKeyRing.getAddress("string") + + const utxos = await bitcoinClient.findAllUnspentTransactionOutputs( + depositorAddress + ) + const utxosWithRaw: (UnspentTransactionOutput & RawTransaction)[] = [] - for (const utxo of inputUtxos) { + for (const utxo of utxos) { const utxoRawTransaction = await bitcoinClient.getRawTransaction( utxo.transactionHash ) @@ -167,21 +152,14 @@ export async function submitDepositTransaction( }) } - const bitcoinNetwork = await bitcoinClient.getNetwork() - const { transactionHash, depositUtxo, rawTransaction } = await assembleDepositTransaction( - bitcoinNetwork, deposit, - depositorPrivateKey, - witness, utxosWithRaw, - fee + depositorPrivateKey, + witness ) - // Note that `broadcast` may fail silently (i.e. no error will be returned, - // even if the transaction is rejected by other nodes and does not enter the - // mempool, for example due to an UTXO being already spent). await bitcoinClient.broadcast(rawTransaction) return { @@ -192,107 +170,57 @@ export async function submitDepositTransaction( /** * Assembles a Bitcoin P2(W)SH deposit transaction. - * @param bitcoinNetwork - The target Bitcoin network. * @param deposit - Details of the deposit. + * @param utxos - UTXOs that should be used as transaction inputs. * @param depositorPrivateKey - Bitcoin private key of the depositor. * @param witness - If true, a witness (P2WSH) transaction will be created. * Otherwise, a legacy P2SH transaction will be made. - * @param inputUtxos - UTXOs to be used for funding the deposit transaction. So - * far only P2WPKH UTXO inputs are supported. - * @param fee - Transaction fee to be subtracted from the sum of the UTXOs' - * values. * @returns The outcome consisting of: * - the deposit transaction hash, * - the deposit UTXO produced by this transaction. * - the deposit transaction in the raw format - * @dev UTXOs are selected for transaction funding based on their types. UTXOs - * with unsupported types are skipped. The selection process stops once - * the sum of the chosen UTXOs meets the required funding amount. - * @throws {Error} When the sum of the selected UTXOs is insufficient to cover - * the deposit amount and transaction fee. */ export async function assembleDepositTransaction( - bitcoinNetwork: BitcoinNetwork, deposit: Deposit, + utxos: (UnspentTransactionOutput & RawTransaction)[], depositorPrivateKey: string, - witness: boolean, - inputUtxos: (UnspentTransactionOutput & RawTransaction)[], - fee: BigNumber + witness: boolean ): Promise<{ transactionHash: TransactionHash depositUtxo: UnspentTransactionOutput rawTransaction: RawTransaction }> { - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - // eslint-disable-next-line new-cap - const depositorKeyPair = ECPairFactory(tinysecp).fromWIF( - depositorPrivateKey, - network + const depositorKeyRing = createKeyRing(depositorPrivateKey) + const depositorAddress = depositorKeyRing.getAddress("string") + + const inputCoins = utxos.map((utxo) => + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), + utxo.outputIndex, + -1 + ) ) - const psbt = new Psbt({ network }) - psbt.setVersion(1) - - const totalExpenses = deposit.amount.add(fee) - let totalInputValue = BigNumber.from(0) - - for (const utxo of inputUtxos) { - const previousOutput = Transaction.fromHex(utxo.transactionHex).outs[ - utxo.outputIndex - ] - const previousOutputValue = previousOutput.value - const previousOutputScript = previousOutput.script - - // TODO: Add support for other utxo types along with unit tests for the - // given type. - if (isP2WPKHScript(previousOutputScript)) { - psbt.addInput({ - hash: utxo.transactionHash.reverse().toBuffer(), - index: utxo.outputIndex, - witnessUtxo: { - script: previousOutputScript, - value: previousOutputValue, - }, - }) - - totalInputValue = totalInputValue.add(utxo.value) - if (totalInputValue.gte(totalExpenses)) { - break - } - } - // Skip UTXO if the type is unsupported. - } + const transaction = new bcoin.MTX() - // Sum of the selected UTXOs must be equal to or greater than the deposit - // amount plus fee. - if (totalInputValue.lt(totalExpenses)) { - throw new Error("Not enough funds in selected UTXOs to fund transaction") - } + const scriptHash = await calculateDepositScriptHash(deposit, witness) - // Add deposit output. - psbt.addOutput({ - address: await calculateDepositAddress(deposit, bitcoinNetwork, witness), + transaction.addOutput({ + script: witness + ? bcoin.Script.fromProgram(0, scriptHash) + : bcoin.Script.fromScripthash(scriptHash), value: deposit.amount.toNumber(), }) - // Add change output if needed. - const changeValue = totalInputValue.sub(totalExpenses) - if (changeValue.gt(0)) { - const depositorAddress = createAddressFromPublicKey( - Hex.from(depositorKeyPair.publicKey), - bitcoinNetwork - ) - psbt.addOutput({ - address: depositorAddress, - value: changeValue.toNumber(), - }) - } + await transaction.fund(inputCoins, { + rate: null, // set null explicitly to always use the default value + changeAddress: depositorAddress, + subtractFee: false, // do not subtract the fee from outputs + }) - psbt.signAllInputs(depositorKeyPair) - psbt.finalizeAllInputs() + transaction.sign(depositorKeyRing) - const transaction = psbt.extractTransaction() - const transactionHash = TransactionHash.from(transaction.getId()) + const transactionHash = TransactionHash.from(transaction.txid()) return { transactionHash, @@ -302,7 +230,7 @@ export async function assembleDepositTransaction( value: deposit.amount, }, rawTransaction: { - transactionHex: transaction.toHex(), + transactionHex: transaction.toRaw().toString("hex"), }, } } @@ -317,31 +245,33 @@ export async function assembleDepositScript( ): Promise { validateDepositScriptParameters(deposit) - const chunks: Stack = [] - - // All HEXes pushed to the script must be un-prefixed - chunks.push(Buffer.from(deposit.depositor.identifierHex, "hex")) - chunks.push(opcodes.OP_DROP) - chunks.push(Buffer.from(deposit.blindingFactor, "hex")) - chunks.push(opcodes.OP_DROP) - chunks.push(opcodes.OP_DUP) - chunks.push(opcodes.OP_HASH160) - chunks.push(Buffer.from(deposit.walletPublicKeyHash, "hex")) - chunks.push(opcodes.OP_EQUAL) - chunks.push(opcodes.OP_IF) - chunks.push(opcodes.OP_CHECKSIG) - chunks.push(opcodes.OP_ELSE) - chunks.push(opcodes.OP_DUP) - chunks.push(opcodes.OP_HASH160) - chunks.push(Buffer.from(deposit.refundPublicKeyHash, "hex")) - chunks.push(opcodes.OP_EQUALVERIFY) - chunks.push(Buffer.from(deposit.refundLocktime, "hex")) - chunks.push(opcodes.OP_CHECKLOCKTIMEVERIFY) - chunks.push(opcodes.OP_DROP) - chunks.push(opcodes.OP_CHECKSIG) - chunks.push(opcodes.OP_ENDIF) - - return script.compile(chunks).toString("hex") + // All HEXes pushed to the script must be un-prefixed. + const script = new bcoin.Script() + script.clear() + script.pushData(Buffer.from(deposit.depositor.identifierHex, "hex")) + script.pushOp(opcodes.OP_DROP) + script.pushData(Buffer.from(deposit.blindingFactor, "hex")) + script.pushOp(opcodes.OP_DROP) + script.pushOp(opcodes.OP_DUP) + script.pushOp(opcodes.OP_HASH160) + script.pushData(Buffer.from(deposit.walletPublicKeyHash, "hex")) + script.pushOp(opcodes.OP_EQUAL) + script.pushOp(opcodes.OP_IF) + script.pushOp(opcodes.OP_CHECKSIG) + script.pushOp(opcodes.OP_ELSE) + script.pushOp(opcodes.OP_DUP) + script.pushOp(opcodes.OP_HASH160) + script.pushData(Buffer.from(deposit.refundPublicKeyHash, "hex")) + script.pushOp(opcodes.OP_EQUALVERIFY) + script.pushData(Buffer.from(deposit.refundLocktime, "hex")) + script.pushOp(opcodes.OP_CHECKLOCKTIMEVERIFY) + script.pushOp(opcodes.OP_DROP) + script.pushOp(opcodes.OP_CHECKSIG) + script.pushOp(opcodes.OP_ENDIF) + script.compile() + + // Return script as HEX string. + return script.toRaw().toString("hex") } // eslint-disable-next-line valid-jsdoc @@ -414,11 +344,11 @@ export async function calculateDepositScriptHash( witness: boolean ): Promise { const script = await assembleDepositScript(deposit) + // Parse the script from HEX string. + const parsedScript = bcoin.Script.fromRaw(Buffer.from(script, "hex")) // If witness script hash should be produced, SHA256 should be used. // Legacy script hash needs HASH160. - return witness - ? computeSha256(Hex.from(script)).toBuffer() - : Buffer.from(computeHash160(script), "hex") + return witness ? parsedScript.sha256() : parsedScript.hash160() } /** @@ -435,28 +365,10 @@ export async function calculateDepositAddress( witness: boolean ): Promise { const scriptHash = await calculateDepositScriptHash(deposit, witness) - const bitcoinNetwork = toBitcoinJsLibNetwork(network) - - if (witness) { - // OP_0 - const p2wshOutput = Buffer.concat([ - Buffer.from([opcodes.OP_0, 0x20]), - scriptHash, - ]) - - return payments.p2wsh({ output: p2wshOutput, network: bitcoinNetwork }) - .address! - } else { - // OP_HASH160 OP_EQUAL - const p2shOutput = Buffer.concat([ - Buffer.from([opcodes.OP_HASH160, 0x14]), - scriptHash, - Buffer.from([opcodes.OP_EQUAL]), - ]) - - return payments.p2sh({ output: p2shOutput, network: bitcoinNetwork }) - .address! - } + const address = witness + ? bcoin.Address.fromWitnessScripthash(scriptHash) + : bcoin.Address.fromScripthash(scriptHash) + return address.toString(toBcoinNetwork(network)) } /** diff --git a/typescript/src/electrum.ts b/typescript/src/electrum.ts index de21961ff..3335f5fc2 100644 --- a/typescript/src/electrum.ts +++ b/typescript/src/electrum.ts @@ -1,8 +1,8 @@ -import { Transaction as Tx, TxInput, TxOutput } from "bitcoinjs-lib" +import bcoin from "bcoin" import pTimeout from "p-timeout" import { Client as BitcoinClient, - computeSha256, + BitcoinNetwork, createOutputScriptFromAddress, RawTransaction, Transaction, @@ -11,8 +11,7 @@ import { TransactionMerkleBranch, TransactionOutput, UnspentTransactionOutput, -} from "./bitcoin" -import { BitcoinNetwork } from "./bitcoin-network" +} from "./lib/bitcoin" import Electrum from "electrum-client-js" import { BigNumber, utils } from "ethers" import { URL } from "url" @@ -233,11 +232,7 @@ export class Client implements BitcoinClient { ): Promise { return this.withElectrum( async (electrum: Electrum) => { - const bitcoinNetwork = await this.getNetwork() - const script = createOutputScriptFromAddress( - address, - bitcoinNetwork - ).toString() + const script = createOutputScriptFromAddress(address).toString() // eslint-disable-next-line camelcase type UnspentOutput = { tx_pos: number; value: number; tx_hash: string } @@ -267,11 +262,7 @@ export class Client implements BitcoinClient { limit?: number ): Promise { return this.withElectrum(async (electrum: Electrum) => { - const bitcoinNetwork = await this.getNetwork() - const script = createOutputScriptFromAddress( - address, - bitcoinNetwork - ).toString() + const script = createOutputScriptFromAddress(address).toString() // eslint-disable-next-line camelcase type HistoryItem = { height: number; tx_hash: string } @@ -338,26 +329,26 @@ export class Client implements BitcoinClient { } // Decode the raw transaction. - const transaction = Tx.fromHex(rawTransaction) + const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") - const inputs = transaction.ins.map( - (input: TxInput): TransactionInput => ({ - transactionHash: TransactionHash.from(input.hash).reverse(), - outputIndex: input.index, - scriptSig: Hex.from(input.script), + const inputs = transaction.inputs.map( + (input: any): TransactionInput => ({ + transactionHash: TransactionHash.from(input.prevout.hash).reverse(), + outputIndex: input.prevout.index, + scriptSig: Hex.from(input.script.toRaw()), }) ) - const outputs = transaction.outs.map( - (output: TxOutput, i: number): TransactionOutput => ({ + const outputs = transaction.outputs.map( + (output: any, i: number): TransactionOutput => ({ outputIndex: i, value: BigNumber.from(output.value), - scriptPubKey: Hex.from(output.script), + scriptPubKey: Hex.from(output.script.toRaw()), }) ) return { - transactionHash: TransactionHash.from(transaction.getId()), + transactionHash: TransactionHash.from(transaction.hash()).reverse(), inputs: inputs, outputs: outputs, } @@ -408,7 +399,7 @@ export class Client implements BitcoinClient { ) // Decode the raw transaction. - const transaction = Tx.fromHex(rawTransaction) + const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") // As a workaround for the problem described in https://github.com/Blockstream/electrs/pull/36 // we need to calculate the number of confirmations based on the latest @@ -426,10 +417,8 @@ export class Client implements BitcoinClient { // If a transaction is unconfirmed (is still in the mempool) the height will // have a value of `0` or `-1`. let txBlockHeight: number = Math.min() - for (const output of transaction.outs) { - const scriptHash: Buffer = computeSha256( - Hex.from(output.script) - ).toBuffer() + for (const output of transaction.outputs) { + const scriptHash: Buffer = output.script.sha256() type HistoryEntry = { // eslint-disable-next-line camelcase diff --git a/typescript/src/ethereum.ts b/typescript/src/ethereum.ts index 6630d8b71..4a7bf48bc 100644 --- a/typescript/src/ethereum.ts +++ b/typescript/src/ethereum.ts @@ -36,7 +36,7 @@ import { readCompactSizeUint, TransactionHash, UnspentTransactionOutput, -} from "./bitcoin" +} from "./lib/bitcoin" import type { OptimisticMintingCancelledEvent, OptimisticMintingFinalizedEvent, diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 7bc6e4922..ed0fdb87e 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -56,9 +56,8 @@ export { Transaction as BitcoinTransaction, TransactionOutput as BitcoinTransactionOutput, locktimeToNumber as BitcoinLocktimeToNumber, -} from "./bitcoin" - -export { BitcoinNetwork } from "./bitcoin-network" + BitcoinNetwork, +} from "./lib/bitcoin" export { Client as ElectrumClient } from "./electrum" diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts new file mode 100644 index 000000000..42bbbdd76 --- /dev/null +++ b/typescript/src/lib/bitcoin/address.ts @@ -0,0 +1,68 @@ +import bcoin, { Script } from "bcoin" +import { Hex } from "../../hex" +import { BitcoinNetwork, toBcoinNetwork } from "./network" + +/** + * Encodes a public key hash into a P2PKH/P2WPKH address. + * @param publicKeyHash - public key hash that will be encoded. Must be an + * unprefixed hex string (without 0x prefix). + * @param witness - If true, a witness public key hash will be encoded and + * P2WPKH address will be returned. Returns P2PKH address otherwise + * @param network - Network the address should be encoded for. + * @returns P2PKH or P2WPKH address encoded from the given public key hash + * @throws Throws an error if network is not supported. + */ +export function encodeToBitcoinAddress( + publicKeyHash: string, + witness: boolean, + network: BitcoinNetwork +): string { + const buffer = Buffer.from(publicKeyHash, "hex") + const bcoinNetwork = toBcoinNetwork(network) + return witness + ? bcoin.Address.fromWitnessPubkeyhash(buffer).toString(bcoinNetwork) + : bcoin.Address.fromPubkeyhash(buffer).toString(bcoinNetwork) +} + +/** + * Decodes P2PKH or P2WPKH address into a public key hash. Throws if the + * provided address is not PKH-based. + * @param address - P2PKH or P2WPKH address that will be decoded. + * @returns Public key hash decoded from the address. This will be an unprefixed + * hex string (without 0x prefix). + */ +export function decodeBitcoinAddress(address: string): string { + const addressObject = new bcoin.Address(address) + + const isPKH = + addressObject.isPubkeyhash() || addressObject.isWitnessPubkeyhash() + if (!isPKH) { + throw new Error("Address must be P2PKH or P2WPKH") + } + + return addressObject.getHash("hex") +} + +/** + * Creates the output script from the BTC address. + * @param address BTC address. + * @returns The un-prefixed and not prepended with length output script. + */ +export function createOutputScriptFromAddress(address: string): Hex { + return Hex.from(Script.fromAddress(address).toRaw().toString("hex")) +} + +/** + * Creates the Bitcoin address from the output script. + * @param script The unprefixed and not prepended with length output script. + * @param network Bitcoin network. + * @returns The Bitcoin address. + */ +export function createAddressFromOutputScript( + script: Hex, + network: BitcoinNetwork = BitcoinNetwork.Mainnet +): string { + return Script.fromRaw(script.toString(), "hex") + .getAddress() + ?.toString(toBcoinNetwork(network)) +} diff --git a/typescript/src/lib/bitcoin/block.ts b/typescript/src/lib/bitcoin/block.ts new file mode 100644 index 000000000..72b325a4e --- /dev/null +++ b/typescript/src/lib/bitcoin/block.ts @@ -0,0 +1,143 @@ +import { BigNumber } from "ethers" +import { Hex } from "../../hex" + +/** + * BlockHeader represents the header of a Bitcoin block. For reference, see: + * https://developer.bitcoin.org/reference/block_chain.html#block-headers. + */ +export interface BlockHeader { + /** + * The block version number that indicates which set of block validation rules + * to follow. The field is 4-byte long. + */ + version: number + + /** + * The hash of the previous block's header. The field is 32-byte long. + */ + previousBlockHeaderHash: Hex + + /** + * The hash derived from the hashes of all transactions included in this block. + * The field is 32-byte long. + */ + merkleRootHash: Hex + + /** + * The Unix epoch time when the miner started hashing the header. The field is + * 4-byte long. + */ + time: number + + /** + * Bits that determine the target threshold this block's header hash must be + * less than or equal to. The field is 4-byte long. + */ + bits: number + + /** + * An arbitrary number miners change to modify the header hash in order to + * produce a hash less than or equal to the target threshold. The field is + * 4-byte long. + */ + nonce: number +} + +/** + * Serializes a BlockHeader to the raw representation. + * @param blockHeader - block header. + * @returns Serialized block header. + */ +export function serializeBlockHeader(blockHeader: BlockHeader): Hex { + const buffer = Buffer.alloc(80) + buffer.writeUInt32LE(blockHeader.version, 0) + blockHeader.previousBlockHeaderHash.toBuffer().copy(buffer, 4) + blockHeader.merkleRootHash.toBuffer().copy(buffer, 36) + buffer.writeUInt32LE(blockHeader.time, 68) + buffer.writeUInt32LE(blockHeader.bits, 72) + buffer.writeUInt32LE(blockHeader.nonce, 76) + return Hex.from(buffer) +} + +/** + * Deserializes a block header in the raw representation to BlockHeader. + * @param rawBlockHeader - BlockHeader in the raw format. + * @returns Block header as a BlockHeader. + */ +export function deserializeBlockHeader(rawBlockHeader: Hex): BlockHeader { + const buffer = rawBlockHeader.toBuffer() + const version = buffer.readUInt32LE(0) + const previousBlockHeaderHash = buffer.slice(4, 36) + const merkleRootHash = buffer.slice(36, 68) + const time = buffer.readUInt32LE(68) + const bits = buffer.readUInt32LE(72) + const nonce = buffer.readUInt32LE(76) + + return { + version: version, + previousBlockHeaderHash: Hex.from(previousBlockHeaderHash), + merkleRootHash: Hex.from(merkleRootHash), + time: time, + bits: bits, + nonce: nonce, + } +} + +/** + * Converts a block header's bits into target. + * @param bits - bits from block header. + * @returns Target as a BigNumber. + */ +export function bitsToTarget(bits: number): BigNumber { + // A serialized 80-byte block header stores the `bits` value as a 4-byte + // little-endian hexadecimal value in a slot including bytes 73, 74, 75, and + // 76. This function's input argument is expected to be a numerical + // representation of that 4-byte value reverted to the big-endian order. + // For example, if the `bits` little-endian value in the header is + // `0xcb04041b`, it must be reverted to the big-endian form `0x1b0404cb` and + // turned to a decimal number `453248203` in order to be used as this + // function's input. + // + // The `bits` 4-byte big-endian representation is a compact value that works + // like a base-256 version of scientific notation. It encodes the target + // exponent in the first byte and the target mantissa in the last three bytes. + // Referring to the previous example, if `bits = 453248203`, the hexadecimal + // representation is `0x1b0404cb` so the exponent is `0x1b` while the mantissa + // is `0x0404cb`. + // + // To extract the exponent, we need to shift right by 3 bytes (24 bits), + // extract the last byte of the result, and subtract 3 (because of the + // mantissa length): + // - 0x1b0404cb >>> 24 = 0x0000001b + // - 0x0000001b & 0xff = 0x1b + // - 0x1b - 3 = 24 (decimal) + // + // To extract the mantissa, we just need to take the last three bytes: + // - 0x1b0404cb & 0xffffff = 0x0404cb = 263371 (decimal) + // + // The final difficulty can be computed as mantissa * 256^exponent: + // - 263371 * 256^24 = + // 1653206561150525499452195696179626311675293455763937233695932416 (decimal) + // + // Sources: + // - https://developer.bitcoin.org/reference/block_chain.html#target-nbits + // - https://wiki.bitcoinsv.io/index.php/Target + + const exponent = ((bits >>> 24) & 0xff) - 3 + const mantissa = bits & 0xffffff + + const target = BigNumber.from(mantissa).mul(BigNumber.from(256).pow(exponent)) + return target +} + +/** + * Converts difficulty target to difficulty. + * @param target - difficulty target. + * @returns Difficulty as a BigNumber. + */ +export function targetToDifficulty(target: BigNumber): BigNumber { + const DIFF1_TARGET = BigNumber.from( + "0xffff0000000000000000000000000000000000000000000000000000" + ) + return DIFF1_TARGET.div(target) +} diff --git a/typescript/src/lib/bitcoin/client.ts b/typescript/src/lib/bitcoin/client.ts new file mode 100644 index 000000000..0d1e10e99 --- /dev/null +++ b/typescript/src/lib/bitcoin/client.ts @@ -0,0 +1,94 @@ +import { BitcoinNetwork } from "./network" +import { + RawTransaction, + Transaction, + TransactionHash, + UnspentTransactionOutput, +} from "./transaction" +import { TransactionMerkleBranch } from "./proof" + +/** + * Represents a Bitcoin client. + */ +export interface Client { + /** + * Gets the network supported by the server the client connected to. + * @returns Bitcoin network. + */ + getNetwork(): Promise + + /** + * Finds all unspent transaction outputs (UTXOs) for given Bitcoin address. + * @param address - Bitcoin address UTXOs should be determined for. + * @returns List of UTXOs. + */ + findAllUnspentTransactionOutputs( + address: string + ): Promise + + /** + * Gets the history of confirmed transactions for given Bitcoin address. + * Returned transactions are sorted from oldest to newest. The returned + * result does not contain unconfirmed transactions living in the mempool + * at the moment of request. + * @param address - Bitcoin address transaction history should be determined for. + * @param limit - Optional parameter that can limit the resulting list to + * a specific number of last transaction. For example, limit = 5 will + * return only the last 5 transactions for the given address. + */ + getTransactionHistory(address: string, limit?: number): Promise + + /** + * Gets the full transaction object for given transaction hash. + * @param transactionHash - Hash of the transaction. + * @returns Transaction object. + */ + getTransaction(transactionHash: TransactionHash): Promise + + /** + * Gets the raw transaction data for given transaction hash. + * @param transactionHash - Hash of the transaction. + * @returns Raw transaction. + */ + getRawTransaction(transactionHash: TransactionHash): Promise + + /** + * Gets the number of confirmations that a given transaction has accumulated + * so far. + * @param transactionHash - Hash of the transaction. + * @returns The number of confirmations. + */ + getTransactionConfirmations(transactionHash: TransactionHash): Promise + + /** + * Gets height of the latest mined block. + * @return Height of the last mined block. + */ + latestBlockHeight(): Promise + + /** + * Gets concatenated chunk of block headers built on a starting block. + * @param blockHeight - Starting block height. + * @param chainLength - Number of subsequent blocks built on the starting + * block. + * @return Concatenation of block headers in a hexadecimal format. + */ + getHeadersChain(blockHeight: number, chainLength: number): Promise + + /** + * Get Merkle branch for a given transaction. + * @param transactionHash - Hash of a transaction. + * @param blockHeight - Height of the block where transaction was confirmed. + * @return Merkle branch. + */ + getTransactionMerkle( + transactionHash: TransactionHash, + blockHeight: number + ): Promise + + /** + * Broadcasts the given transaction over the network. + * @param transaction - Transaction to broadcast. + */ + broadcast(transaction: RawTransaction): Promise +} diff --git a/typescript/src/lib/bitcoin/csuint.ts b/typescript/src/lib/bitcoin/csuint.ts new file mode 100644 index 000000000..42773d137 --- /dev/null +++ b/typescript/src/lib/bitcoin/csuint.ts @@ -0,0 +1,41 @@ +import { Hex } from "../../hex" + +/** + * Reads the leading compact size uint from the provided variable length data. + * + * WARNING: CURRENTLY, THIS FUNCTION SUPPORTS ONLY 1-BYTE COMPACT SIZE UINTS + * AND WILL THROW ON COMPACT SIZE UINTS OF DIFFERENT BYTE LENGTH. + * + * @param varLenData Variable length data preceded by a compact size uint. + * @returns An object holding the value of the compact size uint along with the + * compact size uint byte length. + */ +export function readCompactSizeUint(varLenData: Hex): { + value: number + byteLength: number +} { + // The varLenData is prefixed with the compact size uint. According to the docs + // (https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers) + // a compact size uint can be 1, 3, 5 or 9 bytes. To determine the exact length, + // we need to look at the discriminant byte which is always the first byte of + // the compact size uint. + const discriminant = varLenData.toString().slice(0, 2) + + switch (discriminant) { + case "ff": + case "fe": + case "fd": { + throw new Error( + "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + ) + } + default: { + // The discriminant tells the compact size uint is 1 byte. That means + // the discriminant represent the value itself. + return { + value: parseInt(discriminant, 16), + byteLength: 1, + } + } + } +} diff --git a/typescript/src/lib/bitcoin/hash.ts b/typescript/src/lib/bitcoin/hash.ts new file mode 100644 index 000000000..34a0da163 --- /dev/null +++ b/typescript/src/lib/bitcoin/hash.ts @@ -0,0 +1,37 @@ +import { BigNumber, utils } from "ethers" +import { Hex } from "../../hex" + +/** + * Computes the HASH160 for the given text. + * @param text - Text the HASH160 is computed for. + * @returns Hash as a 20-byte un-prefixed hex string. + */ +export function computeHash160(text: string): string { + const sha256Hash = utils.sha256( + Hex.from(Buffer.from(text, "hex")).toPrefixedString() + ) + const hash160 = utils.ripemd160(sha256Hash) + + return Hex.from(hash160).toString() +} + +/** + * Computes the double SHA256 for the given text. + * @param text - Text the double SHA256 is computed for. + * @returns Hash as a 32-byte un-prefixed hex string. + */ +export function computeHash256(text: Hex): Hex { + const firstHash = utils.sha256(text.toPrefixedString()) + const secondHash = utils.sha256(firstHash) + + return Hex.from(secondHash) +} + +/** + * Converts a hash in hex string in little endian to a BigNumber. + * @param hash - Hash in hex-string format. + * @returns BigNumber representation of the hash. + */ +export function hashLEToBigNumber(hash: Hex): BigNumber { + return BigNumber.from(hash.reverse().toPrefixedString()) +} diff --git a/typescript/src/lib/bitcoin/index.ts b/typescript/src/lib/bitcoin/index.ts new file mode 100644 index 000000000..2cc43b708 --- /dev/null +++ b/typescript/src/lib/bitcoin/index.ts @@ -0,0 +1,9 @@ +export * from "./address" +export * from "./block" +export * from "./client" +export * from "./csuint" +export * from "./hash" +export * from "./key" +export * from "./network" +export * from "./proof" +export * from "./transaction" diff --git a/typescript/src/lib/bitcoin/key.ts b/typescript/src/lib/bitcoin/key.ts new file mode 100644 index 000000000..110991d81 --- /dev/null +++ b/typescript/src/lib/bitcoin/key.ts @@ -0,0 +1,80 @@ +import bcoin from "bcoin" +import wif from "wif" +import { BigNumber } from "ethers" +import { Hex } from "../../hex" + +/** + * Checks whether given public key is a compressed Bitcoin public key. + * @param publicKey - Public key that should be checked. + * @returns True if the key is a compressed Bitcoin public key, false otherwise. + */ +export function isCompressedPublicKey(publicKey: string): boolean { + // Must have 33 bytes and 02 or 03 prefix. + return ( + publicKey.length == 66 && + (publicKey.substring(0, 2) == "02" || publicKey.substring(0, 2) == "03") + ) +} + +/** + * Compresses the given uncompressed Bitcoin public key. + * @param publicKey Uncompressed 64-byte public key as an unprefixed hex string. + * @returns Compressed 33-byte public key prefixed with 02 or 03. + */ +export function compressPublicKey(publicKey: string | Hex): string { + if (typeof publicKey === "string") { + publicKey = Hex.from(publicKey) + } + + publicKey = publicKey.toString() + + // Must have 64 bytes and no prefix. + if (publicKey.length != 128) { + throw new Error( + "The public key parameter must be 64-byte. Neither 0x nor 04 prefix is allowed" + ) + } + + // The X coordinate is the first 32 bytes. + const publicKeyX = publicKey.substring(0, 64) + // The Y coordinate is the next 32 bytes. + const publicKeyY = publicKey.substring(64) + + let prefix: string + if (BigNumber.from(`0x${publicKeyY}`).mod(2).eq(0)) { + prefix = "02" + } else { + prefix = "03" + } + + return `${prefix}${publicKeyX}` +} + +/** + * Checks if given public key hash has proper length (20-byte) + * @param publicKeyHash - text that will be checked for the correct length + * @returns true if the given string is 20-byte long, false otherwise + */ +export function isPublicKeyHashLength(publicKeyHash: string): boolean { + return publicKeyHash.length === 40 +} + +/** + * Creates a Bitcoin key ring based on the given private key. + * @param privateKey Private key that should be used to create the key ring + * @param witness Flag indicating whether the key ring will create witness + * or non-witness addresses + * @returns Bitcoin key ring. + */ +export function createKeyRing( + privateKey: string, + witness: boolean = true +): any { + const decodedPrivateKey = wif.decode(privateKey) + + return new bcoin.KeyRing({ + witness: witness, + privateKey: decodedPrivateKey.privateKey, + compressed: decodedPrivateKey.compressed, + }) +} diff --git a/typescript/src/bitcoin-network.ts b/typescript/src/lib/bitcoin/network.ts similarity index 70% rename from typescript/src/bitcoin-network.ts rename to typescript/src/lib/bitcoin/network.ts index c54c5da38..ce79fad2c 100644 --- a/typescript/src/bitcoin-network.ts +++ b/typescript/src/lib/bitcoin/network.ts @@ -1,5 +1,4 @@ -import { Hex } from "./hex" -import { networks } from "bitcoinjs-lib" +import { Hex } from "../../hex" /** * Bitcoin networks. @@ -46,21 +45,19 @@ export namespace BitcoinNetwork { } /** - * Converts the provided {@link BitcoinNetwork} enumeration to a format expected - * by the `bitcoinjs-lib` library. - * @param bitcoinNetwork - Specified Bitcoin network. - * @returns Network representation compatible with the `bitcoinjs-lib` library. - * @throws An error if the network is not supported by `bitcoinjs-lib`. + * Converts enumerated {@link BitcoinNetwork} to a string expected by the + * {@link https://github.com/keep-network/bcoin/blob/aba6841e43546e8a485e96dc0019d1e788eab2ee/lib/protocol/networks.js#L33| `bcoin` library} + * @param bitcoinNetwork Bitcoin network. + * @returns String representing the given network in bcoin library. + * @throws An error if the network is not supported by bcoin. */ -export function toBitcoinJsLibNetwork( - bitcoinNetwork: BitcoinNetwork -): networks.Network { +export function toBcoinNetwork(bitcoinNetwork: BitcoinNetwork): string { switch (bitcoinNetwork) { case BitcoinNetwork.Mainnet: { - return networks.bitcoin + return "main" } case BitcoinNetwork.Testnet: { - return networks.testnet + return "testnet" } default: { throw new Error(`network not supported`) diff --git a/typescript/src/lib/bitcoin/proof.ts b/typescript/src/lib/bitcoin/proof.ts new file mode 100644 index 000000000..efbf4f8dd --- /dev/null +++ b/typescript/src/lib/bitcoin/proof.ts @@ -0,0 +1,44 @@ +/** + * Data required to perform a proof that a given transaction was included in + * the Bitcoin blockchain. + */ +export interface Proof { + /** + * The merkle proof of transaction inclusion in a block, as an un-prefixed + * hex string. + */ + merkleProof: string + + /** + * Transaction index in the block (0-indexed). + */ + txIndexInBlock: number + + /** + * Single byte-string of 80-byte block headers, lowest height first, as an + * un-prefixed hex string. + */ + bitcoinHeaders: string +} + +/** + * Information about the merkle branch to a confirmed transaction. + */ +export interface TransactionMerkleBranch { + /** + * The height of the block the transaction was confirmed in. + */ + blockHeight: number + + /** + * A list of transaction hashes the current hash is paired with, recursively, + * in order to trace up to obtain the merkle root of the including block, + * the deepest pairing first. Each hash is an unprefixed hex string. + */ + merkle: string[] + + /** + * The 0-based index of the transaction's position in the block. + */ + position: number +} diff --git a/typescript/src/lib/bitcoin/transaction.ts b/typescript/src/lib/bitcoin/transaction.ts new file mode 100644 index 000000000..00a7a03bd --- /dev/null +++ b/typescript/src/lib/bitcoin/transaction.ts @@ -0,0 +1,195 @@ +import { TX } from "bcoin" +import bufio from "bufio" +import { BigNumber } from "ethers" +import { Hex } from "../../hex" + +/** + * Represents a transaction hash (or transaction ID) as an un-prefixed hex + * string. This hash is supposed to have the same byte order as used by the + * Bitcoin block explorers which is the opposite of the byte order used + * by the Bitcoin protocol internally. That means the hash must be reversed in + * the use cases that expect the Bitcoin internal byte order. + */ +export class TransactionHash extends Hex {} + +/** + * Represents a raw transaction. + */ +export interface RawTransaction { + /** + * The full transaction payload as an un-prefixed hex string. + */ + transactionHex: string +} + +/** + * Data about a transaction. + */ +export interface Transaction { + /** + * The transaction hash (or transaction ID) as an un-prefixed hex string. + */ + transactionHash: TransactionHash + + /** + * The vector of transaction inputs. + */ + inputs: TransactionInput[] + + /** + * The vector of transaction outputs. + */ + outputs: TransactionOutput[] +} + +/** + * Data about a transaction outpoint. + */ +export interface TransactionOutpoint { + /** + * The hash of the transaction the outpoint belongs to. + */ + transactionHash: TransactionHash + + /** + * The zero-based index of the output from the specified transaction. + */ + outputIndex: number +} + +/** + * Data about a transaction input. + */ +export type TransactionInput = TransactionOutpoint & { + /** + * The scriptSig that unlocks the specified outpoint for spending. + */ + scriptSig: Hex +} + +/** + * Data about a transaction output. + */ +export interface TransactionOutput { + /** + * The 0-based index of the output. + */ + outputIndex: number + + /** + * The value of the output in satoshis. + */ + value: BigNumber + + /** + * The receiving scriptPubKey. + */ + scriptPubKey: Hex +} + +/** + * Data about an unspent transaction output. + */ +export type UnspentTransactionOutput = TransactionOutpoint & { + /** + * The unspent value in satoshis. + */ + value: BigNumber +} + +/** + * Represents data of decomposed raw transaction. + */ +export interface DecomposedRawTransaction { + /** + * Transaction version as an un-prefixed hex string. + */ + version: string + + /** + * All transaction's inputs prepended by the number of transaction inputs, + * as an un-prefixed hex string. + */ + inputs: string + + /** + * All transaction's outputs prepended by the number of transaction outputs, + * as an un-prefixed hex string. + */ + outputs: string + + /** + * Transaction locktime as an un-prefixed hex string. + */ + locktime: string +} + +/** + * Decomposes a transaction in the raw representation into version, vector of + * inputs, vector of outputs and locktime. + * @param rawTransaction - Transaction in the raw format. + * @returns Transaction data with fields represented as un-prefixed hex strings. + */ +export function decomposeRawTransaction( + rawTransaction: RawTransaction +): DecomposedRawTransaction { + const toHex = (bufferWriter: any) => { + return bufferWriter.render().toString("hex") + } + + const vectorToRaw = (elements: any[]) => { + const buffer = bufio.write() + buffer.writeVarint(elements.length) + for (const element of elements) { + element.toWriter(buffer) + } + return toHex(buffer) + } + + const getTxInputVector = (tx: any) => { + return vectorToRaw(tx.inputs) + } + + const getTxOutputVector = (tx: any) => { + return vectorToRaw(tx.outputs) + } + + const getTxVersion = (tx: any) => { + const buffer = bufio.write() + buffer.writeU32(tx.version) + return toHex(buffer) + } + + const getTxLocktime = (tx: any) => { + const buffer = bufio.write() + buffer.writeU32(tx.locktime) + return toHex(buffer) + } + + const tx = TX.fromRaw(Buffer.from(rawTransaction.transactionHex, "hex"), null) + + return { + version: getTxVersion(tx), + inputs: getTxInputVector(tx), + outputs: getTxOutputVector(tx), + locktime: getTxLocktime(tx), + } +} + +/** + * Converts Bitcoin specific locktime value to a number. The number represents + * either a block height or an Unix timestamp depending on the value. + * + * If the number is less than 500 000 000 it is a block height. + * If the number is greater or equal 500 000 000 it is a Unix timestamp. + * + * @see {@link https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number Documentation} + * + * @param locktimeLE A 4-byte little-endian locktime as an un-prefixed + * hex string {@link: Deposit#refundLocktime}. + * @returns UNIX timestamp in seconds. + */ +export function locktimeToNumber(locktimeLE: Buffer | string): number { + const locktimeBE: Buffer = Hex.from(locktimeLE).reverse().toBuffer() + return BigNumber.from(locktimeBE).toNumber() +} diff --git a/typescript/src/optimistic-minting.ts b/typescript/src/optimistic-minting.ts index 1d1dfd302..79468366f 100644 --- a/typescript/src/optimistic-minting.ts +++ b/typescript/src/optimistic-minting.ts @@ -1,5 +1,5 @@ import { BigNumber } from "ethers" -import { TransactionHash } from "./bitcoin" +import { TransactionHash } from "./lib/bitcoin" import { Identifier, Event, TBTCVault } from "./chain" import { Hex } from "./hex" diff --git a/typescript/src/wallet.ts b/typescript/src/wallet.ts index 947b95436..4e80905ec 100644 --- a/typescript/src/wallet.ts +++ b/typescript/src/wallet.ts @@ -3,12 +3,12 @@ import { Hex } from "./hex" import { Bridge, Event, Identifier } from "./chain" import { Client as BitcoinClient, + BitcoinNetwork, createOutputScriptFromAddress, encodeToBitcoinAddress, TransactionOutput, UnspentTransactionOutput, -} from "./bitcoin" -import { BitcoinNetwork } from "./bitcoin-network" +} from "./lib/bitcoin" /* eslint-disable no-unused-vars */ export enum WalletState { diff --git a/typescript/test/bitcoin-network.test.ts b/typescript/test/bitcoin-network.test.ts index b283c5441..92c258d53 100644 --- a/typescript/test/bitcoin-network.test.ts +++ b/typescript/test/bitcoin-network.test.ts @@ -1,7 +1,9 @@ import { expect } from "chai" -import { BitcoinNetwork, toBitcoinJsLibNetwork } from "../src/bitcoin-network" -import { TransactionHash } from "../src/bitcoin" -import { networks } from "bitcoinjs-lib" +import { + TransactionHash, + BitcoinNetwork, + toBcoinNetwork, +} from "../src/lib/bitcoin" describe("BitcoinNetwork", () => { const testData = [ @@ -10,7 +12,7 @@ describe("BitcoinNetwork", () => { enumValue: "unknown", // any value that doesn't match other supported networks genesisHash: TransactionHash.from("0x00010203"), - expectedToBitcoinJsLibResult: new Error("network not supported"), + expectedToBcoinResult: new Error("network not supported"), }, { enumKey: BitcoinNetwork.Testnet, @@ -18,7 +20,7 @@ describe("BitcoinNetwork", () => { genesisHash: TransactionHash.from( "0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" ), - expectedToBitcoinJsLibResult: networks.testnet, + expectedToBcoinResult: "testnet", }, { enumKey: BitcoinNetwork.Mainnet, @@ -26,12 +28,12 @@ describe("BitcoinNetwork", () => { genesisHash: TransactionHash.from( "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" ), - expectedToBitcoinJsLibResult: networks.bitcoin, + expectedToBcoinResult: "main", }, ] testData.forEach( - ({ enumKey, enumValue, genesisHash, expectedToBitcoinJsLibResult }) => { + ({ enumKey, enumValue, genesisHash, expectedToBcoinResult }) => { context(enumKey, async () => { describe(`toString`, async () => { it(`should return correct value`, async () => { @@ -47,18 +49,16 @@ describe("BitcoinNetwork", () => { }) }) - describe(`toBitcoinJsLibNetwork`, async () => { - if (expectedToBitcoinJsLibResult instanceof Error) { + describe(`toBcoinNetwork`, async () => { + if (expectedToBcoinResult instanceof Error) { it(`should throw an error`, async () => { - expect(() => toBitcoinJsLibNetwork(enumKey)).to.throw( - expectedToBitcoinJsLibResult.message + expect(() => toBcoinNetwork(enumKey)).to.throw( + expectedToBcoinResult.message ) }) } else { - it(`should return ${expectedToBitcoinJsLibResult}`, async () => { - expect(toBitcoinJsLibNetwork(enumKey)).to.be.equal( - expectedToBitcoinJsLibResult - ) + it(`should return ${expectedToBcoinResult}`, async () => { + expect(toBcoinNetwork(enumKey)).to.be.equal(expectedToBcoinResult) }) } }) diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index 03ab74305..0d5e71e38 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -1,5 +1,6 @@ import { expect } from "chai" import { + BitcoinNetwork, compressPublicKey, encodeToBitcoinAddress, decodeBitcoinAddress, @@ -13,23 +14,14 @@ import { targetToDifficulty, createOutputScriptFromAddress, createAddressFromOutputScript, - createAddressFromPublicKey, readCompactSizeUint, computeHash160, - computeSha256, computeHash256, - isP2PKHScript, - isP2WPKHScript, - isP2SHScript, - isP2WSHScript, - decomposeRawTransaction, -} from "../src/bitcoin" +} from "../src/lib/bitcoin" import { calculateDepositRefundLocktime } from "../src/deposit" -import { BitcoinNetwork } from "../src/bitcoin-network" import { Hex } from "../src/hex" import { BigNumber } from "ethers" -import { btcAddresses, btcAddressFromPublicKey } from "./data/bitcoin" -import { depositSweepWithNoMainUtxoAndWitnessOutput } from "./data/deposit-sweep" +import { btcAddresses } from "./data/bitcoin" describe("Bitcoin", () => { describe("compressPublicKey", () => { @@ -101,21 +93,6 @@ describe("Bitcoin", () => { }) }) - describe("computeSha256", () => { - it("should compute hash256 correctly", () => { - const hexValue = Hex.from( - "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" - ) - const expectedSha256 = Hex.from( - "c62e5cb26c97cb52fea7f9965e9ea1f8d41c97773688aa88674e64629fc02901" - ) - - expect(computeSha256(hexValue).toString()).to.be.equal( - expectedSha256.toString() - ) - }) - }) - describe("P2PKH <-> public key hash conversion", () => { const publicKeyHash = "3a38d44d6a0c8d0bb84e0232cc632b7e48c72e0e" const P2WPKHAddress = "bc1q8gudgnt2pjxshwzwqgevccet0eyvwtswt03nuy" @@ -148,10 +125,7 @@ describe("Bitcoin", () => { true, BitcoinNetwork.Mainnet ) - ).to.throw( - 'Expected property "hash" of type Buffer(Length: 20), got ' + - "Buffer(Length: 21)" - ) + ).to.throw("P2WPKH must be 20 bytes") }) }) }) @@ -179,10 +153,7 @@ describe("Bitcoin", () => { false, BitcoinNetwork.Mainnet ) - ).to.throw( - 'Expected property "hash" of type Buffer(Length: 20), got ' + - "Buffer(Length: 21)" - ) + ).to.throw("P2PKH must be 20 bytes") }) }) }) @@ -212,10 +183,7 @@ describe("Bitcoin", () => { true, BitcoinNetwork.Testnet ) - ).to.throw( - 'Expected property "hash" of type Buffer(Length: 20), got ' + - "Buffer(Length: 21)" - ) + ).to.throw("P2WPKH must be 20 bytes") }) }) }) @@ -243,10 +211,7 @@ describe("Bitcoin", () => { false, BitcoinNetwork.Testnet ) - ).to.throw( - 'Expected property "hash" of type Buffer(Length: 20), got ' + - "Buffer(Length: 21)" - ) + ).to.throw("P2PKH must be 20 bytes") }) }) }) @@ -261,21 +226,21 @@ describe("Bitcoin", () => { }) }) - describe("decodeBitcoinAddress", () => { + describe("decodeAddress", () => { context("when network is mainnet", () => { context("when proper P2WPKH address is provided", () => { it("should decode P2WPKH adress correctly", () => { - expect( - decodeBitcoinAddress(P2WPKHAddress, BitcoinNetwork.Mainnet) - ).to.be.equal(publicKeyHash) + expect(decodeBitcoinAddress(P2WPKHAddress)).to.be.equal( + publicKeyHash + ) }) }) context("when proper P2PKH address is provided", () => { it("should decode P2PKH address correctly", () => { - expect( - decodeBitcoinAddress(P2PKHAddress, BitcoinNetwork.Mainnet) - ).to.be.equal(publicKeyHash) + expect(decodeBitcoinAddress(P2PKHAddress)).to.be.equal( + publicKeyHash + ) }) }) @@ -283,10 +248,8 @@ describe("Bitcoin", () => { it("should throw", () => { const bitcoinAddress = "123" + P2PKHAddress - expect(() => - decodeBitcoinAddress(bitcoinAddress, BitcoinNetwork.Mainnet) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" + expect(() => decodeBitcoinAddress(bitcoinAddress)).to.throw( + "Address is too long" ) }) }) @@ -294,13 +257,8 @@ describe("Bitcoin", () => { context("when unsupported P2SH address is provided", () => { it("should throw", () => { expect(() => - decodeBitcoinAddress( - "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", - BitcoinNetwork.Mainnet - ) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" - ) + decodeBitcoinAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX") + ).to.throw("Address must be P2PKH or P2WPKH") }) }) @@ -308,25 +266,9 @@ describe("Bitcoin", () => { it("should throw", () => { expect(() => decodeBitcoinAddress( - "bc1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhsdxuv4m", - BitcoinNetwork.Mainnet + "bc1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhsdxuv4m" ) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" - ) - }) - }) - - context("when address from testnet network is provided", () => { - it("should throw", () => { - expect(() => - decodeBitcoinAddress( - "mkpoZkRvtd3SDGWgUDuXK1aEXZfHRM2gKw", - BitcoinNetwork.Mainnet - ) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" - ) + ).to.throw("Address must be P2PKH or P2WPKH") }) }) }) @@ -334,17 +276,17 @@ describe("Bitcoin", () => { context("when network is testnet", () => { context("when proper P2WPKH address is provided", () => { it("should decode P2WPKH adress correctly", () => { - expect( - decodeBitcoinAddress(P2WPKHAddressTestnet, BitcoinNetwork.Testnet) - ).to.be.equal(publicKeyHash) + expect(decodeBitcoinAddress(P2WPKHAddressTestnet)).to.be.equal( + publicKeyHash + ) }) }) context("when proper P2PKH address is provided", () => { it("should decode P2PKH address correctly", () => { - expect( - decodeBitcoinAddress(P2PKHAddressTestnet, BitcoinNetwork.Testnet) - ).to.be.equal(publicKeyHash) + expect(decodeBitcoinAddress(P2PKHAddressTestnet)).to.be.equal( + publicKeyHash + ) }) }) @@ -352,10 +294,8 @@ describe("Bitcoin", () => { it("should throw", () => { const bitcoinAddress = "123" + P2PKHAddressTestnet - expect(() => - decodeBitcoinAddress(bitcoinAddress, BitcoinNetwork.Testnet) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" + expect(() => decodeBitcoinAddress(bitcoinAddress)).to.throw( + "Address is too long" ) }) }) @@ -363,13 +303,8 @@ describe("Bitcoin", () => { context("when unsupported P2SH address is provided", () => { it("should throw", () => { expect(() => - decodeBitcoinAddress( - "2MyxShnGQ5NifGb8CHYrtmzosRySxZ9pZo5", - BitcoinNetwork.Testnet - ) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" - ) + decodeBitcoinAddress("2MyxShnGQ5NifGb8CHYrtmzosRySxZ9pZo5") + ).to.throw("Address must be P2PKH or P2WPKH") }) }) @@ -377,25 +312,9 @@ describe("Bitcoin", () => { it("should throw", () => { expect(() => decodeBitcoinAddress( - "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05", - BitcoinNetwork.Testnet + "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05" ) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" - ) - }) - }) - - context("when address from mainnet network is provided", () => { - it("should throw", () => { - expect(() => - decodeBitcoinAddress( - "bc1q8gudgnt2pjxshwzwqgevccet0eyvwtswt03nuy", - BitcoinNetwork.Testnet - ) - ).to.throw( - "Address must be P2PKH or P2WPKH valid for given network" - ) + ).to.throw("Address must be P2PKH or P2WPKH") }) }) }) @@ -585,11 +504,7 @@ describe("Bitcoin", () => { ).forEach( ([addressType, { address, scriptPubKey: expectedOutputScript }]) => { it(`should create correct output script for ${addressType} address type`, () => { - const network = - bitcoinNetwork === "mainnet" - ? BitcoinNetwork.Mainnet - : BitcoinNetwork.Testnet - const result = createOutputScriptFromAddress(address, network) + const result = createOutputScriptFromAddress(address) expect(result.toString()).to.eq(expectedOutputScript.toString()) }) @@ -599,7 +514,7 @@ describe("Bitcoin", () => { }) }) - describe("createAddressFromOutputScript", () => { + describe("getAddressFromScriptPubKey", () => { Object.keys(btcAddresses).forEach((bitcoinNetwork) => { context(`with ${bitcoinNetwork} addresses`, () => { Object.entries( @@ -620,30 +535,6 @@ describe("Bitcoin", () => { }) }) - describe("createAddressFromPublicKey", () => { - Object.entries(btcAddressFromPublicKey).forEach( - ([bitcoinNetwork, addressData]) => { - context(`with ${bitcoinNetwork} addresses`, () => { - Object.entries(addressData).forEach( - ([addressType, { publicKey, address }]) => { - it(`should return correct ${addressType} address for ${bitcoinNetwork}`, () => { - const witness = addressType === "P2WPKH" - const result = createAddressFromPublicKey( - publicKey, - bitcoinNetwork === "mainnet" - ? BitcoinNetwork.Mainnet - : BitcoinNetwork.Testnet, - witness - ) - expect(result).to.eq(address) - }) - } - ) - }) - } - ) - }) - describe("readCompactSizeUint", () => { context("when the compact size uint is 1-byte", () => { it("should return the the uint value and byte length", () => { @@ -680,82 +571,4 @@ describe("Bitcoin", () => { }) }) }) - - describe("Bitcoin Script Type", () => { - const testData = [ - { - testFunction: isP2PKHScript, - validScript: Buffer.from( - "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac", - "hex" - ), - name: "P2PKH", - }, - { - testFunction: isP2WPKHScript, - validScript: Buffer.from( - "00148db50eb52063ea9d98b3eac91489a90f738986f6", - "hex" - ), - name: "P2WPKH", - }, - { - testFunction: isP2SHScript, - validScript: Buffer.from( - "a914a9a5f97d5d3c4687a52e90718168270005b369c487", - "hex" - ), - name: "P2SH", - }, - { - testFunction: isP2WSHScript, - validScript: Buffer.from( - "0020b1f83e226979dc9fe74e87f6d303dbb08a27a1c7ce91664033f34c7f2d214cd7", - "hex" - ), - name: "P2WSH", - }, - ] - - testData.forEach(({ testFunction, validScript, name }) => { - describe(`is${name}Script`, () => { - it(`should return true for a valid ${name} script`, () => { - expect(testFunction(validScript)).to.be.true - }) - - it("should return false for other scripts", () => { - testData.forEach((data) => { - if (data.name !== name) { - expect(testFunction(data.validScript)).to.be.false - } - }) - }) - }) - }) - }) -}) - -describe("decomposeRawTransaction", () => { - it("should return correctly decomposed transaction", () => { - const rawTransaction = - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transaction - const decomposedTransaction = decomposeRawTransaction(rawTransaction) - - expect(decomposedTransaction.version).to.be.equal("01000000") - expect(decomposedTransaction.inputs).to.be.equal( - "02bc187be612bc3db8cfcdec56b75e9bc0262ab6eacfe27cc1a699bacd53e3d07400" + - "000000c948304502210089a89aaf3fec97ac9ffa91cdff59829f0cb3ef852a468153" + - "e2c0e2b473466d2e022072902bb923ef016ac52e941ced78f816bf27991c2b73211e" + - "227db27ec200bc0a012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f25" + - "64da4cc29dcf8581d94c5c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508" + - "f9f0c90d000395237576a9148db50eb52063ea9d98b3eac91489a90f738986f68763" + - "ac6776a914e257eccafbc07c381642ce6e7e55120fb077fbed8804e0250162b175ac" + - "68ffffffffdc557e737b6688c5712649b86f7757a722dc3d42786f23b2fa826394df" + - "ec545c0000000000ffffffff" - ) - expect(decomposedTransaction.outputs).to.be.equal( - "01488a0000000000001600148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - expect(decomposedTransaction.locktime).to.be.equal("00000000") - }) }) diff --git a/typescript/test/data/bitcoin.ts b/typescript/test/data/bitcoin.ts index b04baad81..ad0e777d6 100644 --- a/typescript/test/data/bitcoin.ts +++ b/typescript/test/data/bitcoin.ts @@ -1,5 +1,5 @@ -import { BitcoinNetwork } from "../../src/bitcoin-network" -import { Hex } from "../../src/hex" +import { BitcoinNetwork } from "../../src" +import { Hex } from "../../src" export const btcAddresses: Record< Exclude, diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index d01b99344..880f6a896 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -3,7 +3,7 @@ import { RawTransaction, UnspentTransactionOutput, TransactionHash, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" import { Deposit, calculateDepositRefundLocktime } from "../../src/deposit" import { Address } from "../../src/ethereum" diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index 74e001b6b..1992cdb85 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -6,7 +6,7 @@ import { UnspentTransactionOutput, TransactionMerkleBranch, TransactionHash, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" import { calculateDepositRefundLocktime, Deposit } from "../../src/deposit" import { BigNumber } from "ethers" import { Address } from "../../src/ethereum" diff --git a/typescript/test/data/deposit.ts b/typescript/test/data/deposit.ts index 61a9e7b46..42371740f 100644 --- a/typescript/test/data/deposit.ts +++ b/typescript/test/data/deposit.ts @@ -2,7 +2,7 @@ import { RawTransaction, TransactionHash, UnspentTransactionOutput, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" import { BigNumber } from "ethers" /** diff --git a/typescript/test/data/electrum.ts b/typescript/test/data/electrum.ts index f17708bf4..ddc3f6a06 100644 --- a/typescript/test/data/electrum.ts +++ b/typescript/test/data/electrum.ts @@ -4,7 +4,7 @@ import { UnspentTransactionOutput, TransactionMerkleBranch, TransactionHash, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" import { BigNumber } from "ethers" import { Hex } from "../../src" diff --git a/typescript/test/data/proof.ts b/typescript/test/data/proof.ts index a0acdf37c..ab3ae7942 100644 --- a/typescript/test/data/proof.ts +++ b/typescript/test/data/proof.ts @@ -4,7 +4,7 @@ import { Transaction, TransactionHash, TransactionMerkleBranch, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" import { BigNumber } from "ethers" import { Hex } from "../../src" diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index d38780f78..d9e232445 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -8,7 +8,7 @@ import { TransactionMerkleBranch, TransactionHash, createOutputScriptFromAddress, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" import { RedemptionRequest } from "../../src/redemption" import { Address } from "../../src/ethereum" import { BitcoinNetwork, BitcoinTransaction, Hex } from "../../src" From 7e6c6197ea7d932579d11127f00556a225c1256f Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 22 Sep 2023 16:46:15 +0200 Subject: [PATCH 02/43] Transform `chain.ts` file to `lib/contracts` shared lib We just moved `bitcoin.ts` to `lib/bitcoin`. Here we do the same transition for `chain.ts`. --- typescript/src/chain.ts | 444 ------------------ typescript/src/deposit-sweep.ts | 2 +- typescript/src/deposit.ts | 2 +- typescript/src/ethereum.ts | 2 +- typescript/src/lib/contracts/bridge.ts | 184 ++++++++ typescript/src/lib/contracts/chain-event.ts | 63 +++ .../src/lib/contracts/chain-identifier.ts | 15 + typescript/src/lib/contracts/index.ts | 6 + typescript/src/lib/contracts/tbtc-token.ts | 43 ++ typescript/src/lib/contracts/tbtc-vault.ts | 114 +++++ .../src/lib/contracts/wallet-registry.ts | 37 ++ typescript/src/optimistic-minting.ts | 2 +- typescript/src/proof.ts | 2 +- typescript/src/redemption.ts | 109 ++--- typescript/src/wallet.ts | 2 +- typescript/test/deposit-refund.test.ts | 2 +- typescript/test/deposit-sweep.test.ts | 149 +++--- typescript/test/deposit.test.ts | 2 +- typescript/test/electrum.test.ts | 2 +- typescript/test/ethereum.test.ts | 2 +- typescript/test/proof.test.ts | 8 +- typescript/test/redemption.test.ts | 105 ++--- typescript/test/wallet.test.ts | 2 +- 23 files changed, 657 insertions(+), 642 deletions(-) delete mode 100644 typescript/src/chain.ts create mode 100644 typescript/src/lib/contracts/bridge.ts create mode 100644 typescript/src/lib/contracts/chain-event.ts create mode 100644 typescript/src/lib/contracts/chain-identifier.ts create mode 100644 typescript/src/lib/contracts/index.ts create mode 100644 typescript/src/lib/contracts/tbtc-token.ts create mode 100644 typescript/src/lib/contracts/tbtc-vault.ts create mode 100644 typescript/src/lib/contracts/wallet-registry.ts diff --git a/typescript/src/chain.ts b/typescript/src/chain.ts deleted file mode 100644 index 0b840dabe..000000000 --- a/typescript/src/chain.ts +++ /dev/null @@ -1,444 +0,0 @@ -import { BigNumber } from "ethers" -import { - Proof, - UnspentTransactionOutput, - DecomposedRawTransaction, - TransactionHash, -} from "./lib/bitcoin" -import { - DepositRevealedEvent, - DepositScriptParameters, - RevealedDeposit, -} from "./deposit" -import { - OptimisticMintingCancelledEvent, - OptimisticMintingFinalizedEvent, - OptimisticMintingRequest, - OptimisticMintingRequestedEvent, -} from "./optimistic-minting" -import { Hex } from "./hex" -import { RedemptionRequest, RedemptionRequestedEvent } from "./redemption" -import { - DkgResultApprovedEvent, - DkgResultChallengedEvent, - DkgResultSubmittedEvent, - NewWalletRegisteredEvent, - Wallet, -} from "./wallet" -import type { ExecutionLoggerFn } from "./backoff" - -/** - * Represents a generic chain identifier. - */ -export interface Identifier { - /** - * Identifier as an un-prefixed hex string. - */ - identifierHex: string - /** - * Checks if two identifiers are equal. - * - * @param identifier Another identifier - */ - equals(identifier: Identifier): boolean -} - -/** - * Represents a generic chain event. - */ -export interface Event { - /** - * Block number of the event emission. - */ - blockNumber: number - /** - * Block hash of the event emission. - */ - blockHash: Hex - /** - * Transaction hash within which the event was emitted. - */ - transactionHash: Hex -} - -export namespace GetEvents { - /** - * Represents generic options used for getting events from the chain. - */ - export interface Options { - /** - * Block number from which events should be queried. - * If not defined a block number of a contract deployment is used. - */ - fromBlock?: number - /** - * Block number to which events should be queried. - * If not defined the latest block number will be used. - */ - toBlock?: number - /** - * Number of retries in case of an error getting the events. - */ - retries?: number - /** - * Number of blocks for interval length in partial events pulls. - */ - batchedQueryBlockInterval?: number - /** - * A logger function to pass execution messages. - */ - logger?: ExecutionLoggerFn - } - - /** - * Represents a generic function to get events emitted on the chain. - */ - export interface Function { - /** - * Get emitted events. - * @param options Options for getting events. - * @param filterArgs Arguments for events filtering. - * @returns Array of found events. - */ - (options?: Options, ...filterArgs: Array): Promise - } -} - -/** - * Interface for communication with the Bridge on-chain contract. - */ -export interface Bridge { - /** - * Get emitted DepositRevealed events. - * @see GetEventsFunction - */ - getDepositRevealedEvents: GetEvents.Function - - /** - * Submits a deposit sweep transaction proof to the on-chain contract. - * @param sweepTx - Sweep transaction data. - * @param sweepProof - Sweep proof data. - * @param mainUtxo - Data of the wallet's main UTXO. - * @param vault - Optional identifier of the vault the swept deposits should - * be routed in. - */ - submitDepositSweepProof( - sweepTx: DecomposedRawTransaction, - sweepProof: Proof, - mainUtxo: UnspentTransactionOutput, - vault?: Identifier - ): Promise - - /** - * Reveals a given deposit to the on-chain contract. - * @param depositTx - Deposit transaction data - * @param depositOutputIndex - Index of the deposit transaction output that - * funds the revealed deposit - * @param deposit - Data of the revealed deposit - * @returns Transaction hash of the reveal deposit transaction as string - */ - revealDeposit( - depositTx: DecomposedRawTransaction, - depositOutputIndex: number, - deposit: DepositScriptParameters, - vault?: Identifier - ): Promise // TODO: Update to Hex - - /** - * Gets a revealed deposit from the on-chain contract. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @returns Revealed deposit data. - */ - deposits( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise - - /** - * Requests a redemption from the on-chain contract. - * @param walletPublicKey - The Bitcoin public key of the wallet. Must be in the - * compressed form (33 bytes long with 02 or 03 prefix). - * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO - * held by the on-chain contract. - * @param redeemerOutputScript - The output script that the redeemed funds will - * be locked to. Must be un-prefixed and not prepended with length. - * @param amount - The amount to be redeemed in satoshis. - * @returns Empty promise. - */ - requestRedemption( - walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, - redeemerOutputScript: string, - amount: BigNumber - ): Promise - - /** - * Submits a redemption transaction proof to the on-chain contract. - * @param redemptionTx - Redemption transaction data - * @param redemptionProof - Redemption proof data - * @param mainUtxo - Data of the wallet's main UTXO - * @param walletPublicKey - Bitcoin public key of the wallet. Must be in the - * compressed form (33 bytes long with 02 or 03 prefix). - */ - submitRedemptionProof( - redemptionTx: DecomposedRawTransaction, - redemptionProof: Proof, - mainUtxo: UnspentTransactionOutput, - walletPublicKey: string - ): Promise - - /** - * Gets transaction proof difficulty factor from the on-chain contract. - * @dev This number signifies how many confirmations a transaction has to - * accumulate before it can be proven on-chain. - * @returns Proof difficulty factor. - */ - txProofDifficultyFactor(): Promise - - /** - * Gets a pending redemption from the on-chain contract. - * @param walletPublicKey Bitcoin public key of the wallet the request is - * targeted to. Must be in the compressed form (33 bytes long with 02 - * or 03 prefix). - * @param redeemerOutputScript The redeemer output script the redeemed funds - * are supposed to be locked on. Must be un-prefixed and not prepended - * with length. - * @returns Promise with the pending redemption. - */ - pendingRedemptions( - walletPublicKey: string, - redeemerOutputScript: string - ): Promise - - /** - * Gets a timed-out redemption from the on-chain contract. - * @param walletPublicKey Bitcoin public key of the wallet the request is - * targeted to. Must be in the compressed form (33 bytes long with 02 - * or 03 prefix). - * @param redeemerOutputScript The redeemer output script the redeemed funds - * are supposed to be locked on. Must be un-prefixed and not prepended - * with length. - * @returns Promise with the pending redemption. - */ - timedOutRedemptions( - walletPublicKey: string, - redeemerOutputScript: string - ): Promise - - /** - * Gets the public key of the current active wallet. - * @returns Compressed (33 bytes long with 02 or 03 prefix) active wallet's - * public key. If there is no active wallet at the moment, undefined - * is returned. - */ - activeWalletPublicKey(): Promise - - /** - * Get emitted NewWalletRegisteredEvent events. - * @see GetEventsFunction - */ - getNewWalletRegisteredEvents: GetEvents.Function - - /** - * Returns the attached WalletRegistry instance. - */ - walletRegistry(): Promise - - /** - * Gets details about a registered wallet. - * @param walletPublicKeyHash The 20-byte wallet public key hash (computed - * using Bitcoin HASH160 over the compressed ECDSA public key). - * @returns Promise with the wallet details. - */ - wallets(walletPublicKeyHash: Hex): Promise - - /** - * Builds the UTXO hash based on the UTXO components. - * @param utxo UTXO components. - * @returns The hash of the UTXO. - */ - buildUtxoHash(utxo: UnspentTransactionOutput): Hex - - /** - * Get emitted RedemptionRequested events. - * @see GetEventsFunction - */ - getRedemptionRequestedEvents: GetEvents.Function -} - -/** - * Interface for communication with the WalletRegistry on-chain contract. - */ -export interface WalletRegistry { - /** - * Gets the public key for the given wallet. - * @param walletID ID of the wallet. - * @returns Uncompressed public key without the 04 prefix. - */ - getWalletPublicKey(walletID: Hex): Promise - - /** - * Get emitted DkgResultSubmittedEvent events. - * @see GetEventsFunction - */ - getDkgResultSubmittedEvents: GetEvents.Function - - /** - * Get emitted DkgResultApprovedEvent events. - * @see GetEventsFunction - */ - getDkgResultApprovedEvents: GetEvents.Function - - /** - * Get emitted DkgResultChallengedEvent events. - * @see GetEventsFunction - */ - getDkgResultChallengedEvents: GetEvents.Function -} - -/** - * Interface for communication with the TBTCVault on-chain contract. - */ -export interface TBTCVault { - /** - * Gets optimistic minting delay. - * - * The time that needs to pass between the moment the optimistic minting is - * requested and the moment optimistic minting is finalized with minting TBTC. - * @returns Optimistic Minting Delay in seconds. - */ - optimisticMintingDelay(): Promise - - /** - * Gets currently registered minters. - * - * @returns Array containing identifiers of all currently registered minters. - */ - getMinters(): Promise - - /** - * Checks if given identifier is registered as minter. - * - * @param identifier Chain identifier to check. - */ - isMinter(identifier: Identifier): Promise - - /** - * Checks if given identifier is registered as guardian. - * - * @param identifier Chain identifier to check. - */ - isGuardian(identifier: Identifier): Promise - - /** - * Requests optimistic minting for a deposit in an on-chain contract. - * - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @returns Transaction hash of the optimistic mint request transaction. - */ - requestOptimisticMint( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise - - /** - * Cancels optimistic minting for a deposit in an on-chain contract. - * - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @returns Transaction hash of the optimistic mint cancel transaction. - */ - cancelOptimisticMint( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise - - /** - * Finalizes optimistic minting for a deposit in an on-chain contract. - * - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @returns Transaction hash of the optimistic mint finalize transaction. - */ - finalizeOptimisticMint( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise - - /** - * Gets optimistic minting request for a deposit. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @param tbtcVault Handle to the TBTCVault on-chain contract - * @returns Optimistic minting request. - */ - optimisticMintingRequests( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise - - /** - * Get emitted OptimisticMintingRequested events. - * @see GetEventsFunction - */ - getOptimisticMintingRequestedEvents: GetEvents.Function - - /** - * Get emitted OptimisticMintingCancelled events. - * @see GetEventsFunction - */ - getOptimisticMintingCancelledEvents: GetEvents.Function - - /** - * Get emitted OptimisticMintingFinalized events. - * @see GetEventsFunction - */ - getOptimisticMintingFinalizedEvents: GetEvents.Function -} - -/** - * Interface for communication with the TBTC v2 token on-chain contract. - */ -export interface TBTCToken { - /** - * Gets the total supply of the TBTC v2 token. The returned value is in - * ERC 1e18 precision, it has to be converted before using as Bitcoin value - * with 1e8 precision in satoshi. - * @param blockNumber Optional parameter determining the block the total - * supply should be fetched for. If this parameter is not set, the - * total supply is taken for the latest block. - */ - // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 - // precision to Bitcoin in 1e8 precision (satoshi). - totalSupply(blockNumber?: number): Promise - - /** - * Requests redemption in one transacion using the `approveAndCall` function - * from the tBTC on-chain token contract. Then the tBTC token contract calls - * the `receiveApproval` function from the `TBTCVault` contract which burns - * tBTC tokens and requests redemption. - * @param walletPublicKey - The Bitcoin public key of the wallet. Must be in - * the compressed form (33 bytes long with 02 or 03 prefix). - * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO - * held by the on-chain Bridge contract. - * @param redeemerOutputScript - The output script that the redeemed funds - * will be locked to. Must be un-prefixed and not prepended with - * length. - * @param amount - The amount to be redeemed with the precision of the tBTC - * on-chain token contract. - * @returns Transaction hash of the approve and call transaction. - */ - requestRedemption( - walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, - redeemerOutputScript: string, - amount: BigNumber - ): Promise -} diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 9a1e00f7c..5f8e922be 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -11,7 +11,7 @@ import { computeHash160, } from "./lib/bitcoin" import { assembleDepositScript, Deposit } from "./deposit" -import { Bridge, Identifier } from "./chain" +import { Bridge, Identifier } from "./lib/contracts" import { assembleTransactionProof } from "./proof" /** diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index 1deb84609..cf838bb34 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -11,7 +11,7 @@ import { TransactionHash, isPublicKeyHashLength, } from "./lib/bitcoin" -import { Bridge, Event, Identifier } from "./chain" +import { Bridge, Event, Identifier } from "./lib/contracts" import { Hex } from "./hex" const { opcodes } = bcoin.script.common diff --git a/typescript/src/ethereum.ts b/typescript/src/ethereum.ts index 4a7bf48bc..1f05152bd 100644 --- a/typescript/src/ethereum.ts +++ b/typescript/src/ethereum.ts @@ -5,7 +5,7 @@ import { TBTCToken as ChainTBTCToken, Identifier as ChainIdentifier, GetEvents, -} from "./chain" +} from "./lib/contracts" import { BigNumber, constants, diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts new file mode 100644 index 000000000..7452a69d4 --- /dev/null +++ b/typescript/src/lib/contracts/bridge.ts @@ -0,0 +1,184 @@ +import { BigNumber } from "ethers" +import { + Proof, + UnspentTransactionOutput, + DecomposedRawTransaction, + TransactionHash, +} from "../bitcoin" +import { + DepositRevealedEvent, + DepositScriptParameters, + RevealedDeposit, +} from "../../deposit" +import { Hex } from "../../hex" +import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" +import { NewWalletRegisteredEvent, Wallet } from "../../wallet" +import { GetEvents } from "./chain-event" +import { Identifier } from "./chain-identifier" +import { WalletRegistry } from "./wallet-registry" + +/** + * Interface for communication with the Bridge on-chain contract. + */ +export interface Bridge { + /** + * Get emitted DepositRevealed events. + * @see GetEventsFunction + */ + getDepositRevealedEvents: GetEvents.Function + + /** + * Submits a deposit sweep transaction proof to the on-chain contract. + * @param sweepTx - Sweep transaction data. + * @param sweepProof - Sweep proof data. + * @param mainUtxo - Data of the wallet's main UTXO. + * @param vault - Optional identifier of the vault the swept deposits should + * be routed in. + */ + submitDepositSweepProof( + sweepTx: DecomposedRawTransaction, + sweepProof: Proof, + mainUtxo: UnspentTransactionOutput, + vault?: Identifier + ): Promise + + /** + * Reveals a given deposit to the on-chain contract. + * @param depositTx - Deposit transaction data + * @param depositOutputIndex - Index of the deposit transaction output that + * funds the revealed deposit + * @param deposit - Data of the revealed deposit + * @param vault - Optional parameter denoting the vault the given deposit + * should be routed to + * @returns Transaction hash of the reveal deposit transaction as string + */ + revealDeposit( + depositTx: DecomposedRawTransaction, + depositOutputIndex: number, + deposit: DepositScriptParameters, + vault?: Identifier + ): Promise // TODO: Update to Hex + + /** + * Gets a revealed deposit from the on-chain contract. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Revealed deposit data. + */ + deposits( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise + + /** + * Requests a redemption from the on-chain contract. + * @param walletPublicKey - The Bitcoin public key of the wallet. Must be in the + * compressed form (33 bytes long with 02 or 03 prefix). + * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO + * held by the on-chain contract. + * @param redeemerOutputScript - The output script that the redeemed funds will + * be locked to. Must be un-prefixed and not prepended with length. + * @param amount - The amount to be redeemed in satoshis. + * @returns Empty promise. + */ + requestRedemption( + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string, + amount: BigNumber + ): Promise + + /** + * Submits a redemption transaction proof to the on-chain contract. + * @param redemptionTx - Redemption transaction data + * @param redemptionProof - Redemption proof data + * @param mainUtxo - Data of the wallet's main UTXO + * @param walletPublicKey - Bitcoin public key of the wallet. Must be in the + * compressed form (33 bytes long with 02 or 03 prefix). + */ + submitRedemptionProof( + redemptionTx: DecomposedRawTransaction, + redemptionProof: Proof, + mainUtxo: UnspentTransactionOutput, + walletPublicKey: string + ): Promise + + /** + * Gets transaction proof difficulty factor from the on-chain contract. + * @dev This number signifies how many confirmations a transaction has to + * accumulate before it can be proven on-chain. + * @returns Proof difficulty factor. + */ + txProofDifficultyFactor(): Promise + + /** + * Gets a pending redemption from the on-chain contract. + * @param walletPublicKey Bitcoin public key of the wallet the request is + * targeted to. Must be in the compressed form (33 bytes long with 02 + * or 03 prefix). + * @param redeemerOutputScript The redeemer output script the redeemed funds + * are supposed to be locked on. Must be un-prefixed and not prepended + * with length. + * @returns Promise with the pending redemption. + */ + pendingRedemptions( + walletPublicKey: string, + redeemerOutputScript: string + ): Promise + + /** + * Gets a timed-out redemption from the on-chain contract. + * @param walletPublicKey Bitcoin public key of the wallet the request is + * targeted to. Must be in the compressed form (33 bytes long with 02 + * or 03 prefix). + * @param redeemerOutputScript The redeemer output script the redeemed funds + * are supposed to be locked on. Must be un-prefixed and not prepended + * with length. + * @returns Promise with the pending redemption. + */ + timedOutRedemptions( + walletPublicKey: string, + redeemerOutputScript: string + ): Promise + + /** + * Gets the public key of the current active wallet. + * @returns Compressed (33 bytes long with 02 or 03 prefix) active wallet's + * public key. If there is no active wallet at the moment, undefined + * is returned. + */ + activeWalletPublicKey(): Promise + + /** + * Get emitted NewWalletRegisteredEvent events. + * @see GetEventsFunction + */ + getNewWalletRegisteredEvents: GetEvents.Function + + /** + * Returns the attached WalletRegistry instance. + */ + walletRegistry(): Promise + + /** + * Gets details about a registered wallet. + * @param walletPublicKeyHash The 20-byte wallet public key hash (computed + * using Bitcoin HASH160 over the compressed ECDSA public key). + * @returns Promise with the wallet details. + */ + wallets(walletPublicKeyHash: Hex): Promise + + /** + * Builds the UTXO hash based on the UTXO components. + * @param utxo UTXO components. + * @returns The hash of the UTXO. + */ + buildUtxoHash(utxo: UnspentTransactionOutput): Hex + + /** + * Get emitted RedemptionRequested events. + * @see GetEventsFunction + */ + getRedemptionRequestedEvents: GetEvents.Function +} diff --git a/typescript/src/lib/contracts/chain-event.ts b/typescript/src/lib/contracts/chain-event.ts new file mode 100644 index 000000000..09f5cb022 --- /dev/null +++ b/typescript/src/lib/contracts/chain-event.ts @@ -0,0 +1,63 @@ +import { Hex } from "../../hex" +import { ExecutionLoggerFn } from "../../backoff" + +/** + * Represents a generic chain event. + */ +export interface Event { + /** + * Block number of the event emission. + */ + blockNumber: number + /** + * Block hash of the event emission. + */ + blockHash: Hex + /** + * Transaction hash within which the event was emitted. + */ + transactionHash: Hex +} + +export namespace GetEvents { + /** + * Represents generic options used for getting events from the chain. + */ + export interface Options { + /** + * Block number from which events should be queried. + * If not defined a block number of a contract deployment is used. + */ + fromBlock?: number + /** + * Block number to which events should be queried. + * If not defined the latest block number will be used. + */ + toBlock?: number + /** + * Number of retries in case of an error getting the events. + */ + retries?: number + /** + * Number of blocks for interval length in partial events pulls. + */ + batchedQueryBlockInterval?: number + /** + * A logger function to pass execution messages. + */ + logger?: ExecutionLoggerFn + } + + /** + * Represents a generic function to get events emitted on the chain. + */ + export interface Function { + /** + * Get emitted events. + * @param options Options for getting events. + * @param filterArgs Arguments for events filtering. + * @returns Array of found events. + */ + (options?: Options, ...filterArgs: Array): Promise + } +} diff --git a/typescript/src/lib/contracts/chain-identifier.ts b/typescript/src/lib/contracts/chain-identifier.ts new file mode 100644 index 000000000..3591f7bd1 --- /dev/null +++ b/typescript/src/lib/contracts/chain-identifier.ts @@ -0,0 +1,15 @@ +/** + * Represents a generic chain identifier. + */ +export interface Identifier { + /** + * Identifier as an un-prefixed hex string. + */ + identifierHex: string + /** + * Checks if two identifiers are equal. + * + * @param identifier Another identifier + */ + equals(identifier: Identifier): boolean +} diff --git a/typescript/src/lib/contracts/index.ts b/typescript/src/lib/contracts/index.ts new file mode 100644 index 000000000..58f2565df --- /dev/null +++ b/typescript/src/lib/contracts/index.ts @@ -0,0 +1,6 @@ +export * from "./bridge" +export * from "./chain-event" +export * from "./chain-identifier" +export * from "./tbtc-token" +export * from "./tbtc-vault" +export * from "./wallet-registry" diff --git a/typescript/src/lib/contracts/tbtc-token.ts b/typescript/src/lib/contracts/tbtc-token.ts new file mode 100644 index 000000000..6133bb01d --- /dev/null +++ b/typescript/src/lib/contracts/tbtc-token.ts @@ -0,0 +1,43 @@ +import { BigNumber } from "ethers" +import { UnspentTransactionOutput } from "../bitcoin" +import { Hex } from "../../hex" + +/** + * Interface for communication with the TBTC v2 token on-chain contract. + */ +export interface TBTCToken { + /** + * Gets the total supply of the TBTC v2 token. The returned value is in + * ERC 1e18 precision, it has to be converted before using as Bitcoin value + * with 1e8 precision in satoshi. + * @param blockNumber Optional parameter determining the block the total + * supply should be fetched for. If this parameter is not set, the + * total supply is taken for the latest block. + */ + // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 + // precision to Bitcoin in 1e8 precision (satoshi). + totalSupply(blockNumber?: number): Promise + + /** + * Requests redemption in one transacion using the `approveAndCall` function + * from the tBTC on-chain token contract. Then the tBTC token contract calls + * the `receiveApproval` function from the `TBTCVault` contract which burns + * tBTC tokens and requests redemption. + * @param walletPublicKey - The Bitcoin public key of the wallet. Must be in + * the compressed form (33 bytes long with 02 or 03 prefix). + * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO + * held by the on-chain Bridge contract. + * @param redeemerOutputScript - The output script that the redeemed funds + * will be locked to. Must be un-prefixed and not prepended with + * length. + * @param amount - The amount to be redeemed with the precision of the tBTC + * on-chain token contract. + * @returns Transaction hash of the approve and call transaction. + */ + requestRedemption( + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string, + amount: BigNumber + ): Promise +} diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts new file mode 100644 index 000000000..0ce82041f --- /dev/null +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -0,0 +1,114 @@ +import { TransactionHash } from "../bitcoin" +import { Hex } from "../../hex" +import { + OptimisticMintingCancelledEvent, + OptimisticMintingFinalizedEvent, + OptimisticMintingRequest, + OptimisticMintingRequestedEvent, +} from "../../optimistic-minting" +import { Identifier } from "./chain-identifier" +import { GetEvents } from "./chain-event" + +/** + * Interface for communication with the TBTCVault on-chain contract. + */ +export interface TBTCVault { + /** + * Gets optimistic minting delay. + * + * The time that needs to pass between the moment the optimistic minting is + * requested and the moment optimistic minting is finalized with minting TBTC. + * @returns Optimistic Minting Delay in seconds. + */ + optimisticMintingDelay(): Promise + + /** + * Gets currently registered minters. + * + * @returns Array containing identifiers of all currently registered minters. + */ + getMinters(): Promise + + /** + * Checks if given identifier is registered as minter. + * + * @param identifier Chain identifier to check. + */ + isMinter(identifier: Identifier): Promise + + /** + * Checks if given identifier is registered as guardian. + * + * @param identifier Chain identifier to check. + */ + isGuardian(identifier: Identifier): Promise + + /** + * Requests optimistic minting for a deposit in an on-chain contract. + * + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Transaction hash of the optimistic mint request transaction. + */ + requestOptimisticMint( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise + + /** + * Cancels optimistic minting for a deposit in an on-chain contract. + * + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Transaction hash of the optimistic mint cancel transaction. + */ + cancelOptimisticMint( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise + + /** + * Finalizes optimistic minting for a deposit in an on-chain contract. + * + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Transaction hash of the optimistic mint finalize transaction. + */ + finalizeOptimisticMint( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise + + /** + * Gets optimistic minting request for a deposit. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Optimistic minting request. + */ + optimisticMintingRequests( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise + + /** + * Get emitted OptimisticMintingRequested events. + * @see GetEventsFunction + */ + getOptimisticMintingRequestedEvents: GetEvents.Function + + /** + * Get emitted OptimisticMintingCancelled events. + * @see GetEventsFunction + */ + getOptimisticMintingCancelledEvents: GetEvents.Function + + /** + * Get emitted OptimisticMintingFinalized events. + * @see GetEventsFunction + */ + getOptimisticMintingFinalizedEvents: GetEvents.Function +} diff --git a/typescript/src/lib/contracts/wallet-registry.ts b/typescript/src/lib/contracts/wallet-registry.ts new file mode 100644 index 000000000..ef0e1711b --- /dev/null +++ b/typescript/src/lib/contracts/wallet-registry.ts @@ -0,0 +1,37 @@ +import { Hex } from "../../hex" +import { + DkgResultApprovedEvent, + DkgResultChallengedEvent, + DkgResultSubmittedEvent, +} from "../../wallet" +import { GetEvents } from "./chain-event" + +/** + * Interface for communication with the WalletRegistry on-chain contract. + */ +export interface WalletRegistry { + /** + * Gets the public key for the given wallet. + * @param walletID ID of the wallet. + * @returns Uncompressed public key without the 04 prefix. + */ + getWalletPublicKey(walletID: Hex): Promise + + /** + * Get emitted DkgResultSubmittedEvent events. + * @see GetEventsFunction + */ + getDkgResultSubmittedEvents: GetEvents.Function + + /** + * Get emitted DkgResultApprovedEvent events. + * @see GetEventsFunction + */ + getDkgResultApprovedEvents: GetEvents.Function + + /** + * Get emitted DkgResultChallengedEvent events. + * @see GetEventsFunction + */ + getDkgResultChallengedEvents: GetEvents.Function +} diff --git a/typescript/src/optimistic-minting.ts b/typescript/src/optimistic-minting.ts index 79468366f..04506cebf 100644 --- a/typescript/src/optimistic-minting.ts +++ b/typescript/src/optimistic-minting.ts @@ -1,6 +1,6 @@ import { BigNumber } from "ethers" import { TransactionHash } from "./lib/bitcoin" -import { Identifier, Event, TBTCVault } from "./chain" +import { Identifier, Event, TBTCVault } from "./lib/contracts" import { Hex } from "./hex" /** diff --git a/typescript/src/proof.ts b/typescript/src/proof.ts index f07cf8161..7d7f739b7 100644 --- a/typescript/src/proof.ts +++ b/typescript/src/proof.ts @@ -11,7 +11,7 @@ import { hashLEToBigNumber, serializeBlockHeader, BlockHeader, -} from "./bitcoin" +} from "./lib/bitcoin" import { BigNumber } from "ethers" import { Hex } from "./hex" diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index 7e99a85ea..c5e9045d6 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -1,21 +1,17 @@ +import bcoin from "bcoin" import { BigNumber } from "ethers" import { - createAddressFromPublicKey, + BitcoinNetwork, + createKeyRing, decomposeRawTransaction, RawTransaction, UnspentTransactionOutput, Client as BitcoinClient, TransactionHash, - isP2PKHScript, - isP2WPKHScript, -} from "./bitcoin" -import { Bridge, Event, Identifier, TBTCToken } from "./chain" +} from "./lib/bitcoin" +import { Bridge, Event, Identifier, TBTCToken } from "./lib/contracts" import { assembleTransactionProof } from "./proof" import { determineWalletMainUtxo, WalletState } from "./wallet" -import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./bitcoin-network" -import { Psbt, Transaction } from "bitcoinjs-lib" -import { ECPairFactory } from "ecpair" -import * as tinysecp from "tiny-secp256k1" import { Hex } from "./hex" /** @@ -144,25 +140,15 @@ export async function submitRedemptionTransaction( transactionHex: mainUtxoRawTransaction.transactionHex, } - const bitcoinNetwork = await bitcoinClient.getNetwork() - - // eslint-disable-next-line new-cap - const walletKeyPair = ECPairFactory(tinysecp).fromWIF( - walletPrivateKey, - toBitcoinJsLibNetwork(bitcoinNetwork) - ) - const walletPublicKey = walletKeyPair.publicKey.toString("hex") - const redemptionRequests = await getWalletRedemptionRequests( bridge, - walletPublicKey, + createKeyRing(walletPrivateKey).getPublicKey().toString("hex"), redeemerOutputScripts, "pending" ) const { transactionHash, newMainUtxo, rawTransaction } = await assembleRedemptionTransaction( - bitcoinNetwork, walletPrivateKey, mainUtxoWithRaw, redemptionRequests, @@ -256,7 +242,6 @@ async function getWalletRedemptionRequests( * - there is at least one redemption * - the `requestedAmount` in each redemption request is greater than * the sum of its `txFee` and `treasuryFee` - * @param bitcoinNetwork - The target Bitcoin network. * @param walletPrivateKey - The private key of the wallet in the WIF format * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO held * by the on-chain Bridge contract @@ -269,7 +254,6 @@ async function getWalletRedemptionRequests( * - the redemption transaction in the raw format */ export async function assembleRedemptionTransaction( - bitcoinNetwork: BitcoinNetwork, walletPrivateKey: string, mainUtxo: UnspentTransactionOutput & RawTransaction, redemptionRequests: RedemptionRequest[], @@ -283,46 +267,19 @@ export async function assembleRedemptionTransaction( throw new Error("There must be at least one request to redeem") } - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - // eslint-disable-next-line new-cap - const walletKeyPair = ECPairFactory(tinysecp).fromWIF( - walletPrivateKey, - network - ) - const walletAddress = createAddressFromPublicKey( - Hex.from(walletKeyPair.publicKey), - bitcoinNetwork, - witness - ) + const walletKeyRing = createKeyRing(walletPrivateKey, witness) + const walletAddress = walletKeyRing.getAddress("string") - const psbt = new Psbt({ network }) - psbt.setVersion(1) - - // Add input (current main UTXO). - const previousOutput = Transaction.fromHex(mainUtxo.transactionHex).outs[ - mainUtxo.outputIndex + // Use the main UTXO as the single transaction input + const inputCoins = [ + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), + mainUtxo.outputIndex, + -1 + ), ] - const previousOutputScript = previousOutput.script - const previousOutputValue = previousOutput.value - - if (isP2PKHScript(previousOutputScript)) { - psbt.addInput({ - hash: mainUtxo.transactionHash.reverse().toBuffer(), - index: mainUtxo.outputIndex, - nonWitnessUtxo: Buffer.from(mainUtxo.transactionHex, "hex"), - }) - } else if (isP2WPKHScript(previousOutputScript)) { - psbt.addInput({ - hash: mainUtxo.transactionHash.reverse().toBuffer(), - index: mainUtxo.outputIndex, - witnessUtxo: { - script: previousOutputScript, - value: previousOutputValue, - }, - }) - } else { - throw new Error("Unexpected main UTXO type") - } + + const transaction = new bcoin.MTX() let txTotalFee = BigNumber.from(0) let totalOutputsValue = BigNumber.from(0) @@ -346,34 +303,44 @@ export async function assembleRedemptionTransaction( // use the proposed fee and add the difference to outputs proportionally. txTotalFee = txTotalFee.add(request.txMaxFee) - psbt.addOutput({ - script: Buffer.from(request.redeemerOutputScript, "hex"), + transaction.addOutput({ + script: bcoin.Script.fromRaw( + Buffer.from(request.redeemerOutputScript, "hex") + ), value: outputValue.toNumber(), }) } - // If there is a change output, add it to the transaction. + // If there is a change output, add it explicitly to the transaction. + // If we did not add this output explicitly, the bcoin library would add it + // anyway during funding, but if the value of the change output was very low, + // the library would consider it "dust" and add it to the fee rather than + // create a new output. const changeOutputValue = mainUtxo.value .sub(totalOutputsValue) .sub(txTotalFee) if (changeOutputValue.gt(0)) { - psbt.addOutput({ - address: walletAddress, + transaction.addOutput({ + script: bcoin.Script.fromAddress(walletAddress), value: changeOutputValue.toNumber(), }) } - psbt.signAllInputs(walletKeyPair) - psbt.finalizeAllInputs() + await transaction.fund(inputCoins, { + changeAddress: walletAddress, + hardFee: txTotalFee.toNumber(), + subtractFee: false, + }) + + transaction.sign(walletKeyRing) - const transaction = psbt.extractTransaction() - const transactionHash = TransactionHash.from(transaction.getId()) + const transactionHash = TransactionHash.from(transaction.txid()) // If there is a change output, it will be the new wallet's main UTXO. const newMainUtxo = changeOutputValue.gt(0) ? { transactionHash, // It was the last output added to the transaction. - outputIndex: transaction.outs.length - 1, + outputIndex: transaction.outputs.length - 1, value: changeOutputValue, } : undefined @@ -382,7 +349,7 @@ export async function assembleRedemptionTransaction( transactionHash, newMainUtxo, rawTransaction: { - transactionHex: transaction.toHex(), + transactionHex: transaction.toRaw().toString("hex"), }, } } diff --git a/typescript/src/wallet.ts b/typescript/src/wallet.ts index 4e80905ec..a292b0574 100644 --- a/typescript/src/wallet.ts +++ b/typescript/src/wallet.ts @@ -1,6 +1,6 @@ import { BigNumber } from "ethers" import { Hex } from "./hex" -import { Bridge, Event, Identifier } from "./chain" +import { Bridge, Event, Identifier } from "./lib/contracts" import { Client as BitcoinClient, BitcoinNetwork, diff --git a/typescript/test/deposit-refund.test.ts b/typescript/test/deposit-refund.test.ts index 04111e4c5..a6e30ccf3 100644 --- a/typescript/test/deposit-refund.test.ts +++ b/typescript/test/deposit-refund.test.ts @@ -5,7 +5,7 @@ import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) import { expect } from "chai" import { submitDepositRefundTransaction } from "../src/deposit-refund" -import { TransactionHash, RawTransaction } from "./bitcoin" +import { TransactionHash, RawTransaction } from "./lib/bitcoin" import { refunderPrivateKey, depositRefundOfWitnessDepositAndWitnessRefunderAddress, diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts index f37a961c6..e7727de85 100644 --- a/typescript/test/deposit-sweep.test.ts +++ b/typescript/test/deposit-sweep.test.ts @@ -4,8 +4,13 @@ import { TransactionHash, UnspentTransactionOutput, Transaction, -} from "../src/bitcoin" -import { testnetWalletAddress, testnetWalletPrivateKey } from "./data/deposit" +} from "../src/lib/bitcoin" +import { + testnetDepositScripthashAddress, + testnetDepositWitnessScripthashAddress, + testnetWalletAddress, + testnetWalletPrivateKey, +} from "./data/deposit" import { depositSweepWithWitnessMainUtxoAndWitnessOutput, depositSweepWithNoMainUtxoAndWitnessOutput, @@ -16,7 +21,7 @@ import { } from "./data/deposit-sweep" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { MockBridge } from "./utils/mock-bridge" -import { txToJSON } from "./utils/helpers" +import bcoin from "bcoin" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) @@ -26,7 +31,6 @@ import { submitDepositSweepProof, submitDepositSweepTransaction, } from "../src/deposit-sweep" -import { BitcoinNetwork } from "../src/bitcoin-network" describe("Sweep", () => { const fee = BigNumber.from(1600) @@ -35,6 +39,8 @@ describe("Sweep", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { + bcoin.set("testnet") + bitcoinClient = new MockBitcoinClient() }) @@ -373,7 +379,6 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, witness, @@ -389,10 +394,8 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() @@ -403,23 +406,26 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(2) const p2shInput = txJSON.inputs[0] - expect(p2shInput.hash).to.be.equal( + expect(p2shInput.prevout.hash).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2shInput.index).to.be.equal( + expect(p2shInput.prevout.index).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.empty + expect(p2shInput.witness).to.be.equal("00") expect(p2shInput.script.length).to.be.greaterThan(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2shInput.address).to.be.equal(testnetDepositScripthashAddress) const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.hash).to.be.equal( + expect(p2wshInput.prevout.hash).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() ) - expect(p2wshInput.index).to.be.equal( + expect(p2wshInput.prevout.index).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo .outputIndex ) @@ -427,6 +433,11 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wshInput.witness.length).to.be.greaterThan(0) expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // witness script hash + expect(p2wshInput.address).to.be.equal( + testnetDepositWitnessScripthashAddress + ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -497,7 +508,6 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, witness, @@ -515,10 +525,8 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() @@ -529,10 +537,10 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(3) const p2wkhInput = txJSON.inputs[0] - expect(p2wkhInput.hash).to.be.equal( + expect(p2wkhInput.prevout.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() ) - expect(p2wkhInput.index).to.be.equal( + expect(p2wkhInput.prevout.index).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo .outputIndex ) @@ -540,25 +548,33 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wkhInput.witness.length).to.be.greaterThan(0) expect(p2wkhInput.script.length).to.be.equal(0) + // The input comes from the main UTXO so the input should be the + // wallet's address + expect(p2wkhInput.address).to.be.equal(testnetWalletAddress) const p2shInput = txJSON.inputs[1] - expect(p2shInput.hash).to.be.equal( + expect(p2shInput.prevout.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2shInput.index).to.be.equal( + expect(p2shInput.prevout.index).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.empty + expect(p2shInput.witness).to.be.equal("00") expect(p2shInput.script.length).to.be.greaterThan(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2shInput.address).to.be.equal( + testnetDepositScripthashAddress + ) const p2wshInput = txJSON.inputs[2] - expect(p2wshInput.hash).to.be.equal( + expect(p2wshInput.prevout.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() ) - expect(p2wshInput.index).to.be.equal( + expect(p2wshInput.prevout.index).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo .outputIndex ) @@ -566,6 +582,11 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wshInput.witness.length).to.be.greaterThan(0) expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // witness script hash + expect(p2wshInput.address).to.be.equal( + testnetDepositWitnessScripthashAddress + ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -637,7 +658,6 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, witness, @@ -655,10 +675,8 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() @@ -668,24 +686,11 @@ describe("Sweep", () => { // Validate inputs. expect(txJSON.inputs.length).to.be.equal(2) - const p2pkhInput = txJSON.inputs[0] // main UTXO - expect(p2pkhInput.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() - ) - expect(p2pkhInput.index).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - .outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2pkhInput.witness).to.be.empty - expect(p2pkhInput.script.length).to.be.greaterThan(0) - - const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.hash).to.be.equal( + const p2wshInput = txJSON.inputs[0] + expect(p2wshInput.prevout.hash).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2wshInput.index).to.be.equal( + expect(p2wshInput.prevout.index).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0] .utxo.outputIndex ) @@ -693,6 +698,29 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wshInput.witness.length).to.be.greaterThan(0) expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2wshInput.address).to.be.equal( + "tb1qk8urugnf08wfle6wslmdxq7mkz9z0gw8e6gkvspn7dx87tfpfntshdm7qr" + ) + + const p2pkhInput = txJSON.inputs[1] // main UTXO + expect(p2pkhInput.prevout.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() + ) + expect(p2pkhInput.prevout.index).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + .outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2pkhInput.witness).to.be.equal("00") + expect(p2pkhInput.script.length).to.be.greaterThan(0) + // The input comes from the main UTXO so the input should be the + // wallet's address + expect(p2pkhInput.address).to.be.equal( + "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" + ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -763,7 +791,6 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, witness, @@ -780,10 +807,9 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + expect(txJSON.hash).to.be.equal( depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep.transactionHash.toString() ) @@ -793,17 +819,22 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(1) const p2shInput = txJSON.inputs[0] - expect(p2shInput.hash).to.be.equal( + expect(p2shInput.prevout.hash).to.be.equal( depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2shInput.index).to.be.equal( + expect(p2shInput.prevout.index).to.be.equal( depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.empty + expect(p2shInput.witness).to.be.equal("00") expect(p2shInput.script.length).to.be.greaterThan(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2shInput.address).to.be.equal( + "2N8iF1pRndihBzgLDna9MfRhmqktwTdHejA" + ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -847,7 +878,6 @@ describe("Sweep", () => { it("should revert", async () => { await expect( assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, true, @@ -876,7 +906,6 @@ describe("Sweep", () => { it("should revert", async () => { await expect( assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, witness, @@ -902,7 +931,6 @@ describe("Sweep", () => { it("should revert", async () => { await expect( assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, true, @@ -942,7 +970,6 @@ describe("Sweep", () => { it("should revert", async () => { await expect( assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, true, @@ -967,7 +994,6 @@ describe("Sweep", () => { it("should revert", async () => { await expect( assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, anotherPrivateKey, true, @@ -1001,7 +1027,6 @@ describe("Sweep", () => { it("should revert", async () => { await expect( assembleDepositSweepTransaction( - BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, true, @@ -1018,6 +1043,8 @@ describe("Sweep", () => { let bridge: MockBridge beforeEach(async () => { + bcoin.set("testnet") + bitcoinClient = new MockBitcoinClient() bridge = new MockBridge() diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index a1b2c705b..fd94c1f04 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -12,7 +12,7 @@ import { RawTransaction, TransactionHash, UnspentTransactionOutput, -} from "../src/bitcoin" +} from "../src/lib/bitcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { assembleDepositScript, diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index e0ad8909b..51d7bb788 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -3,7 +3,7 @@ import { Client as ElectrumClient, computeScriptHash, } from "../src/electrum" -import { BitcoinNetwork } from "../src/bitcoin-network" +import { BitcoinNetwork } from "../src/lib/bitcoin" import { testnetAddress, testnetHeadersChain, diff --git a/typescript/test/ethereum.test.ts b/typescript/test/ethereum.test.ts index 780180ffa..98215fc04 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/ethereum.test.ts @@ -10,7 +10,7 @@ import { abi as TBTCTokenABI } from "@keep-network/tbtc-v2/artifacts/TBTC.json" import { abi as WalletRegistryABI } from "@keep-network/ecdsa/artifacts/WalletRegistry.json" import { MockProvider } from "@ethereum-waffle/provider" import { waffleChai } from "@ethereum-waffle/chai" -import { TransactionHash, computeHash160 } from "../src/bitcoin" +import { TransactionHash, computeHash160 } from "../src/lib/bitcoin" import { Hex } from "../src/hex" chai.use(waffleChai) diff --git a/typescript/test/proof.test.ts b/typescript/test/proof.test.ts index 68cef604e..ead678177 100644 --- a/typescript/test/proof.test.ts +++ b/typescript/test/proof.test.ts @@ -1,5 +1,10 @@ import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import { serializeBlockHeader, Transaction, BlockHeader } from "../src/bitcoin" +import { + serializeBlockHeader, + Transaction, + BlockHeader, + Proof, +} from "../src/lib/bitcoin" import { Hex } from "../src/hex" import { singleInputProofTestData, @@ -15,7 +20,6 @@ import { validateTransactionProof, splitHeaders, } from "../src/proof" -import { Proof } from "./bitcoin" import { expect } from "chai" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index 802942f7b..b83ad3161 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -1,9 +1,11 @@ import { + BitcoinNetwork, Transaction, RawTransaction, TransactionHash, UnspentTransactionOutput, -} from "../src/bitcoin" +} from "../src/lib/bitcoin" +import bcoin from "bcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { walletPrivateKey, @@ -30,13 +32,11 @@ import { submitRedemptionProof, submitRedemptionTransaction, } from "../src/redemption" -import { txToJSON } from "./utils/helpers" import { MockBridge } from "./utils/mock-bridge" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" import { expect } from "chai" import { BigNumberish, BigNumber } from "ethers" -import { BitcoinNetwork } from "../src/bitcoin-network" import { Wallet } from "../src/wallet" import { MockTBTCToken } from "./utils/mock-tbtc-token" import { BitcoinTransaction } from "../src" @@ -53,6 +53,8 @@ describe("Redemption", () => { const token: MockTBTCToken = new MockTBTCToken() beforeEach(async () => { + bcoin.set("testnet") + await requestRedemption( walletPublicKey, mainUtxo, @@ -80,6 +82,7 @@ describe("Redemption", () => { let bridge: MockBridge beforeEach(async () => { + bcoin.set("testnet") bitcoinClient = new MockBitcoinClient() bridge = new MockBridge() }) @@ -495,7 +498,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -510,10 +512,8 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -525,14 +525,17 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -608,7 +611,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -623,10 +625,8 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -638,14 +638,17 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -720,7 +723,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -735,10 +737,8 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -750,14 +750,17 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -832,7 +835,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -847,10 +849,8 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -862,14 +862,17 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -943,7 +946,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -956,10 +958,8 @@ describe("Redemption", () => { expect(transaction).to.be.eql(data.expectedRedemption.transaction) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -971,14 +971,15 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(5) @@ -1102,7 +1103,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -1115,10 +1115,8 @@ describe("Redemption", () => { expect(transaction).to.be.eql(data.expectedRedemption.transaction) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1130,14 +1128,15 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -1210,7 +1209,6 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests, @@ -1223,10 +1221,9 @@ describe("Redemption", () => { expect(transaction).to.be.eql(data.expectedRedemption.transaction) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() ) @@ -1237,14 +1234,15 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal( + expect(input.prevout.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.prevout.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -1300,7 +1298,6 @@ describe("Redemption", () => { it("should revert", async () => { await expect( assembleRedemptionTransaction( - BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, [], // empty list of redemption requests @@ -1324,6 +1321,8 @@ describe("Redemption", () => { let bridge: MockBridge beforeEach(async () => { + bcoin.set("testnet") + bitcoinClient = new MockBitcoinClient() bridge = new MockBridge() diff --git a/typescript/test/wallet.test.ts b/typescript/test/wallet.test.ts index 277b07e5a..678c396c4 100644 --- a/typescript/test/wallet.test.ts +++ b/typescript/test/wallet.test.ts @@ -3,7 +3,7 @@ import { MockBridge } from "./utils/mock-bridge" import { BitcoinNetwork, BitcoinTransaction, Hex } from "../src" import { determineWalletMainUtxo, Wallet } from "../src/wallet" import { expect } from "chai" -import { encodeToBitcoinAddress } from "../src/bitcoin" +import { encodeToBitcoinAddress } from "../src/lib/bitcoin" import { BigNumber } from "ethers" describe("Wallet", () => { From 97cfb7e9e86cb546150d60302cf7e87f452862fa Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 22 Sep 2023 16:58:26 +0200 Subject: [PATCH 03/43] Transform `electrum.ts` file to `lib/electrum` shared lib --- typescript/src/index.ts | 2 +- typescript/src/{electrum.ts => lib/electrum/client.ts} | 6 +++--- typescript/src/lib/electrum/index.ts | 1 + typescript/test/electrum.test.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) rename typescript/src/{electrum.ts => lib/electrum/client.ts} (99%) create mode 100644 typescript/src/lib/electrum/index.ts diff --git a/typescript/src/index.ts b/typescript/src/index.ts index ed0fdb87e..a130de012 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -59,7 +59,7 @@ export { BitcoinNetwork, } from "./lib/bitcoin" -export { Client as ElectrumClient } from "./electrum" +export { Client as ElectrumClient } from "./lib/electrum" export { Bridge as EthereumBridge, diff --git a/typescript/src/electrum.ts b/typescript/src/lib/electrum/client.ts similarity index 99% rename from typescript/src/electrum.ts rename to typescript/src/lib/electrum/client.ts index 3335f5fc2..f3d69cd7a 100644 --- a/typescript/src/electrum.ts +++ b/typescript/src/lib/electrum/client.ts @@ -11,12 +11,12 @@ import { TransactionMerkleBranch, TransactionOutput, UnspentTransactionOutput, -} from "./lib/bitcoin" +} from "../bitcoin" import Electrum from "electrum-client-js" import { BigNumber, utils } from "ethers" import { URL } from "url" -import { Hex } from "./hex" -import { backoffRetrier, RetrierFn } from "./backoff" +import { Hex } from "../../hex" +import { backoffRetrier, RetrierFn } from "../../backoff" /** * Represents a set of credentials required to establish an Electrum connection. diff --git a/typescript/src/lib/electrum/index.ts b/typescript/src/lib/electrum/index.ts new file mode 100644 index 000000000..a5017494f --- /dev/null +++ b/typescript/src/lib/electrum/index.ts @@ -0,0 +1 @@ +export * from "./client" diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index 51d7bb788..8d643309e 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -2,7 +2,7 @@ import { Credentials as ElectrumCredentials, Client as ElectrumClient, computeScriptHash, -} from "../src/electrum" +} from "../src/lib/electrum" import { BitcoinNetwork } from "../src/lib/bitcoin" import { testnetAddress, From c09233fcbf8e8678b36b0b110c3f65ab8b71cf69 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 22 Sep 2023 17:40:31 +0200 Subject: [PATCH 04/43] Transform `ethereum.ts` file to `lib/ethereum` shared lib --- typescript/src/ethereum.ts | 1308 ----------------- typescript/src/index.ts | 2 +- typescript/src/lib/ethereum/address.ts | 32 + typescript/src/lib/ethereum/bridge.ts | 618 ++++++++ .../ethereum/contract-handle.ts} | 142 +- typescript/src/lib/ethereum/index.ts | 10 + typescript/src/lib/ethereum/tbtc-token.ts | 129 ++ typescript/src/lib/ethereum/tbtc-vault.ts | 286 ++++ .../src/lib/ethereum/wallet-registry.ts | 136 ++ typescript/test/data/deposit-refund.ts | 2 +- typescript/test/data/deposit-sweep.ts | 2 +- typescript/test/data/redemption.ts | 10 +- typescript/test/deposit.test.ts | 77 +- typescript/test/ethereum.test.ts | 2 +- typescript/test/utils/mock-bitcoin-client.ts | 4 +- typescript/test/utils/mock-bridge.ts | 14 +- typescript/test/utils/mock-tbtc-token.ts | 4 +- 17 files changed, 1404 insertions(+), 1374 deletions(-) delete mode 100644 typescript/src/ethereum.ts create mode 100644 typescript/src/lib/ethereum/address.ts create mode 100644 typescript/src/lib/ethereum/bridge.ts rename typescript/src/{ethereum-helpers.ts => lib/ethereum/contract-handle.ts} (56%) create mode 100644 typescript/src/lib/ethereum/index.ts create mode 100644 typescript/src/lib/ethereum/tbtc-token.ts create mode 100644 typescript/src/lib/ethereum/tbtc-vault.ts create mode 100644 typescript/src/lib/ethereum/wallet-registry.ts diff --git a/typescript/src/ethereum.ts b/typescript/src/ethereum.ts deleted file mode 100644 index 1f05152bd..000000000 --- a/typescript/src/ethereum.ts +++ /dev/null @@ -1,1308 +0,0 @@ -import { - Bridge as ChainBridge, - WalletRegistry as ChainWalletRegistry, - TBTCVault as ChainTBTCVault, - TBTCToken as ChainTBTCToken, - Identifier as ChainIdentifier, - GetEvents, -} from "./lib/contracts" -import { - BigNumber, - constants, - Contract as EthersContract, - ContractTransaction, - Event as EthersEvent, - providers, - Signer, - utils, -} from "ethers" -import BridgeDeployment from "@keep-network/tbtc-v2/artifacts/Bridge.json" -import WalletRegistryDeployment from "@keep-network/ecdsa/artifacts/WalletRegistry.json" -import TBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" -import TBTCDeployment from "@keep-network/tbtc-v2/artifacts/TBTC.json" -import { backoffRetrier } from "./backoff" -import { - DepositScriptParameters, - RevealedDeposit, - DepositRevealedEvent, -} from "./deposit" -import { getEvents, sendWithRetry } from "./ethereum-helpers" -import { RedemptionRequest, RedemptionRequestedEvent } from "./redemption" -import { - compressPublicKey, - computeHash160, - DecomposedRawTransaction, - Proof, - readCompactSizeUint, - TransactionHash, - UnspentTransactionOutput, -} from "./lib/bitcoin" -import type { - OptimisticMintingCancelledEvent, - OptimisticMintingFinalizedEvent, - OptimisticMintingRequest, - OptimisticMintingRequestedEvent, -} from "./optimistic-minting" - -import type { - Bridge as ContractBridge, - Deposit as ContractDeposit, - Redemption as ContractRedemption, - Wallets, -} from "../typechain/Bridge" -import type { WalletRegistry as ContractWalletRegistry } from "../typechain/WalletRegistry" -import type { TBTCVault as ContractTBTCVault } from "../typechain/TBTCVault" -import type { TBTC as ContractTBTC } from "../typechain/TBTC" -import { Hex } from "./hex" -import { - DkgResultApprovedEvent, - DkgResultChallengedEvent, - DkgResultSubmittedEvent, - NewWalletRegisteredEvent, - Wallet, - WalletState, -} from "./wallet" - -type ContractDepositRequest = ContractDeposit.DepositRequestStructOutput - -type ContractRedemptionRequest = - ContractRedemption.RedemptionRequestStructOutput - -type ContractOptimisticMintingRequest = { - requestedAt: BigNumber - finalizedAt: BigNumber -} - -/** - * Contract deployment artifact. - * @see [hardhat-deploy#Deployment](https://github.com/wighawag/hardhat-deploy/blob/0c969e9a27b4eeff9f5ccac7e19721ef2329eed2/types.ts#L358)} - */ -export interface Deployment { - /** - * Address of the deployed contract. - */ - address: string - /** - * Contract's ABI. - */ - abi: any[] - /** - * Deployment transaction receipt. - */ - receipt: { - /** - * Number of block in which the contract was deployed. - */ - blockNumber: number - } -} - -/** - * Represents an Ethereum address. - */ -// TODO: Make Address extends Hex -export class Address implements ChainIdentifier { - readonly identifierHex: string - - // TODO: Make constructor private - constructor(address: string) { - let validAddress: string - - try { - validAddress = utils.getAddress(address) - } catch (e) { - throw new Error(`Invalid Ethereum address`) - } - - this.identifierHex = validAddress.substring(2).toLowerCase() - } - - static from(address: string): Address { - return new Address(address) - } - - // TODO: Remove once extends Hex - equals(otherValue: Address): boolean { - return this.identifierHex === otherValue.identifierHex - } -} - -/** - * Represents a config set required to connect an Ethereum contract. - */ -export interface ContractConfig { - /** - * Address of the Ethereum contract as a 0x-prefixed hex string. - * Optional parameter, if not provided the value will be resolved from the - * contract artifact. - */ - address?: string - /** - * Signer - will return a Contract which will act on behalf of that signer. The signer will sign all contract transactions. - * Provider - will return a downgraded Contract which only has read-only access (i.e. constant calls) - */ - signerOrProvider: Signer | providers.Provider - /** - * Number of a block in which the contract was deployed. - * Optional parameter, if not provided the value will be resolved from the - * contract artifact. - */ - deployedAtBlockNumber?: number -} - -/** - * Deployed Ethereum contract - */ -class EthereumContract { - /** - * Ethers instance of the deployed contract. - */ - protected readonly _instance: T - /** - * Number of a block within which the contract was deployed. Value is read from - * the contract deployment artifact. It can be overwritten by setting a - * {@link ContractConfig.deployedAtBlockNumber} property. - */ - protected readonly _deployedAtBlockNumber: number - /** - * Number of retries for ethereum requests. - */ - protected readonly _totalRetryAttempts: number - - /** - * @param config Configuration for contract instance initialization. - * @param deployment Contract Deployment artifact. - * @param totalRetryAttempts Number of retries for ethereum requests. - */ - constructor( - config: ContractConfig, - deployment: Deployment, - totalRetryAttempts = 3 - ) { - this._instance = new EthersContract( - config.address ?? utils.getAddress(deployment.address), - `${JSON.stringify(deployment.abi)}`, - config.signerOrProvider - ) as T - - this._deployedAtBlockNumber = - config.deployedAtBlockNumber ?? deployment.receipt.blockNumber - - this._totalRetryAttempts = totalRetryAttempts - } - - /** - * Get address of the contract instance. - * @returns Address of this contract instance. - */ - getAddress(): Address { - return Address.from(this._instance.address) - } - - /** - * Get events emitted by the Ethereum contract. - * It starts searching from provided block number. If the {@link GetEvents.Options#fromBlock} - * option is missing it looks for a contract's defined property - * {@link _deployedAtBlockNumber}. If the property is missing starts searching - * from block `0`. - * @param eventName Name of the event. - * @param options Options for events fetching. - * @param filterArgs Arguments for events filtering. - * @returns Array of found events. - */ - async getEvents( - eventName: string, - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - // TODO: Test if we need a workaround for querying events from big range in chunks, - // see: https://github.com/keep-network/tbtc-monitoring/blob/e169357d7b8c638d4eaf73d52aa8f53ee4aebc1d/src/lib/ethereum-helper.js#L44-L73 - return backoffRetrier( - options?.retries ?? this._totalRetryAttempts - )(async () => { - return await getEvents( - this._instance, - this._instance.filters[eventName](...filterArgs), - options?.fromBlock ?? this._deployedAtBlockNumber, - options?.toBlock, - options?.batchedQueryBlockInterval, - options?.logger - ) - }) - } -} - -// TODO: Refactor code structure as discussed in https://github.com/keep-network/tbtc-v2/pull/460#discussion_r1063383624. - -/** - * Implementation of the Ethereum Bridge handle. - * @see {ChainBridge} for reference. - */ -export class Bridge - extends EthereumContract - implements ChainBridge -{ - constructor(config: ContractConfig) { - super(config, BridgeDeployment) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#getDepositRevealedEvents} - */ - async getDepositRevealedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events: EthersEvent[] = await this.getEvents( - "DepositRevealed", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - fundingTxHash: TransactionHash.from( - event.args!.fundingTxHash - ).reverse(), - fundingOutputIndex: BigNumber.from( - event.args!.fundingOutputIndex - ).toNumber(), - depositor: new Address(event.args!.depositor), - amount: BigNumber.from(event.args!.amount), - blindingFactor: Hex.from(event.args!.blindingFactor).toString(), - walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(), - refundPublicKeyHash: Hex.from(event.args!.refundPubKeyHash).toString(), - refundLocktime: Hex.from(event.args!.refundLocktime).toString(), - vault: - event.args!.vault === constants.AddressZero - ? undefined - : new Address(event.args!.vault), - } - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#pendingRedemptions} - */ - async pendingRedemptions( - walletPublicKey: string, - redeemerOutputScript: string - ): Promise { - const redemptionKey = Bridge.buildRedemptionKey( - computeHash160(walletPublicKey), - redeemerOutputScript - ) - - const request: ContractRedemptionRequest = - await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.pendingRedemptions(redemptionKey) - } - ) - - return this.parseRedemptionRequest(request, redeemerOutputScript) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#timedOutRedemptions} - */ - async timedOutRedemptions( - walletPublicKey: string, - redeemerOutputScript: string - ): Promise { - const redemptionKey = Bridge.buildRedemptionKey( - computeHash160(walletPublicKey), - redeemerOutputScript - ) - - const request: ContractRedemptionRequest = - await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.timedOutRedemptions(redemptionKey) - } - ) - - return this.parseRedemptionRequest(request, redeemerOutputScript) - } - - /** - * Builds a redemption key required to refer a redemption request. - * @param walletPublicKeyHash The wallet public key hash that identifies the - * pending redemption (along with the redeemer output script). Must be - * unprefixed. - * @param redeemerOutputScript The redeemer output script that identifies the - * pending redemption (along with the wallet public key hash). Must be - * un-prefixed and not prepended with length. - * @returns The redemption key. - */ - static buildRedemptionKey( - walletPublicKeyHash: string, - redeemerOutputScript: string - ): string { - // Convert the output script to raw bytes buffer. - const rawRedeemerOutputScript = Buffer.from(redeemerOutputScript, "hex") - // Prefix the output script bytes buffer with 0x and its own length. - const prefixedRawRedeemerOutputScript = `0x${Buffer.concat([ - Buffer.from([rawRedeemerOutputScript.length]), - rawRedeemerOutputScript, - ]).toString("hex")}` - // Build the redemption key by using the 0x-prefixed wallet PKH and - // prefixed output script. - return utils.solidityKeccak256( - ["bytes32", "bytes20"], - [ - utils.solidityKeccak256(["bytes"], [prefixedRawRedeemerOutputScript]), - `0x${walletPublicKeyHash}`, - ] - ) - } - - /** - * Parses a redemption request using data fetched from the on-chain contract. - * @param request Data of the request. - * @param redeemerOutputScript The redeemer output script that identifies the - * pending redemption (along with the wallet public key hash). Must be - * un-prefixed and not prepended with length. - * @returns Parsed redemption request. - */ - private parseRedemptionRequest( - request: ContractRedemptionRequest, - redeemerOutputScript: string - ): RedemptionRequest { - return { - redeemer: new Address(request.redeemer), - redeemerOutputScript: redeemerOutputScript, - requestedAmount: BigNumber.from(request.requestedAmount), - treasuryFee: BigNumber.from(request.treasuryFee), - txMaxFee: BigNumber.from(request.txMaxFee), - requestedAt: BigNumber.from(request.requestedAt).toNumber(), - } - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#revealDeposit} - */ - async revealDeposit( - depositTx: DecomposedRawTransaction, - depositOutputIndex: number, - deposit: DepositScriptParameters, - vault?: ChainIdentifier - ): Promise { - const depositTxParam = { - version: `0x${depositTx.version}`, - inputVector: `0x${depositTx.inputs}`, - outputVector: `0x${depositTx.outputs}`, - locktime: `0x${depositTx.locktime}`, - } - - const revealParam = { - fundingOutputIndex: depositOutputIndex, - blindingFactor: `0x${deposit.blindingFactor}`, - walletPubKeyHash: `0x${deposit.walletPublicKeyHash}`, - refundPubKeyHash: `0x${deposit.refundPublicKeyHash}`, - refundLocktime: `0x${deposit.refundLocktime}`, - vault: vault ? `0x${vault.identifierHex}` : constants.AddressZero, - } - - const tx = await sendWithRetry( - async () => { - return await this._instance.revealDeposit(depositTxParam, revealParam) - }, - this._totalRetryAttempts, - undefined, - ["Deposit already revealed"] - ) - - return tx.hash - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#submitDepositSweepProof} - */ - async submitDepositSweepProof( - sweepTx: DecomposedRawTransaction, - sweepProof: Proof, - mainUtxo: UnspentTransactionOutput, - vault?: ChainIdentifier - ): Promise { - const sweepTxParam = { - version: `0x${sweepTx.version}`, - inputVector: `0x${sweepTx.inputs}`, - outputVector: `0x${sweepTx.outputs}`, - locktime: `0x${sweepTx.locktime}`, - } - - const sweepProofParam = { - merkleProof: `0x${sweepProof.merkleProof}`, - txIndexInBlock: sweepProof.txIndexInBlock, - bitcoinHeaders: `0x${sweepProof.bitcoinHeaders}`, - } - - const mainUtxoParam = { - // The Ethereum Bridge expects this hash to be in the Bitcoin internal - // byte order. - txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), - txOutputIndex: mainUtxo.outputIndex, - txOutputValue: mainUtxo.value, - } - - const vaultParam = vault - ? `0x${vault.identifierHex}` - : constants.AddressZero - - await sendWithRetry(async () => { - return await this._instance.submitDepositSweepProof( - sweepTxParam, - sweepProofParam, - mainUtxoParam, - vaultParam - ) - }, this._totalRetryAttempts) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#txProofDifficultyFactor} - */ - async txProofDifficultyFactor(): Promise { - const txProofDifficultyFactor: BigNumber = await backoffRetrier( - this._totalRetryAttempts - )(async () => { - return await this._instance.txProofDifficultyFactor() - }) - - return txProofDifficultyFactor.toNumber() - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#requestRedemption} - */ - async requestRedemption( - walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, - redeemerOutputScript: string, - amount: BigNumber - ): Promise { - const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` - - const mainUtxoParam = { - // The Ethereum Bridge expects this hash to be in the Bitcoin internal - // byte order. - txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), - txOutputIndex: mainUtxo.outputIndex, - txOutputValue: mainUtxo.value, - } - - // Convert the output script to raw bytes buffer. - const rawRedeemerOutputScript = Buffer.from(redeemerOutputScript, "hex") - // Prefix the output script bytes buffer with 0x and its own length. - const prefixedRawRedeemerOutputScript = `0x${Buffer.concat([ - Buffer.from([rawRedeemerOutputScript.length]), - rawRedeemerOutputScript, - ]).toString("hex")}` - - await sendWithRetry(async () => { - return await this._instance.requestRedemption( - walletPublicKeyHash, - mainUtxoParam, - prefixedRawRedeemerOutputScript, - amount - ) - }, this._totalRetryAttempts) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#submitRedemptionProof} - */ - async submitRedemptionProof( - redemptionTx: DecomposedRawTransaction, - redemptionProof: Proof, - mainUtxo: UnspentTransactionOutput, - walletPublicKey: string - ): Promise { - const redemptionTxParam = { - version: `0x${redemptionTx.version}`, - inputVector: `0x${redemptionTx.inputs}`, - outputVector: `0x${redemptionTx.outputs}`, - locktime: `0x${redemptionTx.locktime}`, - } - - const redemptionProofParam = { - merkleProof: `0x${redemptionProof.merkleProof}`, - txIndexInBlock: redemptionProof.txIndexInBlock, - bitcoinHeaders: `0x${redemptionProof.bitcoinHeaders}`, - } - - const mainUtxoParam = { - // The Ethereum Bridge expects this hash to be in the Bitcoin internal - // byte order. - txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), - txOutputIndex: mainUtxo.outputIndex, - txOutputValue: mainUtxo.value, - } - - const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` - - await sendWithRetry(async () => { - return await this._instance.submitRedemptionProof( - redemptionTxParam, - redemptionProofParam, - mainUtxoParam, - walletPublicKeyHash - ) - }, this._totalRetryAttempts) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#deposits} - */ - async deposits( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise { - const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) - - const deposit: ContractDepositRequest = - await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.deposits(depositKey) - } - ) - - return this.parseRevealedDeposit(deposit) - } - - /** - * Builds the deposit key required to refer a revealed deposit. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @returns Deposit key. - */ - static buildDepositKey( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): string { - const prefixedReversedDepositTxHash = depositTxHash - .reverse() - .toPrefixedString() - - return utils.solidityKeccak256( - ["bytes32", "uint32"], - [prefixedReversedDepositTxHash, depositOutputIndex] - ) - } - - /** - * Parses a revealed deposit using data fetched from the on-chain contract. - * @param deposit Data of the revealed deposit. - * @returns Parsed revealed deposit. - */ - private parseRevealedDeposit( - deposit: ContractDepositRequest - ): RevealedDeposit { - return { - depositor: new Address(deposit.depositor), - amount: BigNumber.from(deposit.amount), - vault: - deposit.vault === constants.AddressZero - ? undefined - : new Address(deposit.vault), - revealedAt: BigNumber.from(deposit.revealedAt).toNumber(), - sweptAt: BigNumber.from(deposit.sweptAt).toNumber(), - treasuryFee: BigNumber.from(deposit.treasuryFee), - } - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#activeWalletPublicKey} - */ - async activeWalletPublicKey(): Promise { - const activeWalletPublicKeyHash: string = await backoffRetrier( - this._totalRetryAttempts - )(async () => { - return await this._instance.activeWalletPubKeyHash() - }) - - if ( - activeWalletPublicKeyHash === "0x0000000000000000000000000000000000000000" - ) { - // If there is no active wallet currently, return undefined. - return undefined - } - - const { walletPublicKey } = await this.wallets( - Hex.from(activeWalletPublicKeyHash) - ) - - return walletPublicKey.toString() - } - - private async getWalletCompressedPublicKey(ecdsaWalletID: Hex): Promise { - const walletRegistry = await this.walletRegistry() - const uncompressedPublicKey = await walletRegistry.getWalletPublicKey( - ecdsaWalletID - ) - - return Hex.from(compressPublicKey(uncompressedPublicKey)) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#getNewWalletRegisteredEvents} - */ - async getNewWalletRegisteredEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events: EthersEvent[] = await this.getEvents( - "NewWalletRegistered", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - ecdsaWalletID: Hex.from(event.args!.ecdsaWalletID), - walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash), - } - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#walletRegistry} - */ - async walletRegistry(): Promise { - const { ecdsaWalletRegistry } = await backoffRetrier<{ - ecdsaWalletRegistry: string - }>(this._totalRetryAttempts)(async () => { - return await this._instance.contractReferences() - }) - - return new WalletRegistry({ - address: ecdsaWalletRegistry, - signerOrProvider: this._instance.signer || this._instance.provider, - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#wallets} - */ - async wallets(walletPublicKeyHash: Hex): Promise { - const wallet = await backoffRetrier( - this._totalRetryAttempts - )(async () => { - return await this._instance.wallets( - walletPublicKeyHash.toPrefixedString() - ) - }) - - return this.parseWalletDetails(wallet) - } - - /** - * Parses a wallet data using data fetched from the on-chain contract. - * @param wallet Data of the wallet. - * @returns Parsed wallet data. - */ - private async parseWalletDetails( - wallet: Wallets.WalletStructOutput - ): Promise { - const ecdsaWalletID = Hex.from(wallet.ecdsaWalletID) - - return { - ecdsaWalletID, - walletPublicKey: await this.getWalletCompressedPublicKey(ecdsaWalletID), - mainUtxoHash: Hex.from(wallet.mainUtxoHash), - pendingRedemptionsValue: wallet.pendingRedemptionsValue, - createdAt: wallet.createdAt, - movingFundsRequestedAt: wallet.movingFundsRequestedAt, - closingStartedAt: wallet.closingStartedAt, - pendingMovedFundsSweepRequestsCount: - wallet.pendingMovedFundsSweepRequestsCount, - state: WalletState.parse(wallet.state), - movingFundsTargetWalletsCommitmentHash: Hex.from( - wallet.movingFundsTargetWalletsCommitmentHash - ), - } - } - - // eslint-disable-next-line valid-jsdoc - /** - * Builds the UTXO hash based on the UTXO components. UTXO hash is computed as - * `keccak256(txHash | txOutputIndex | txOutputValue)`. - * - * @see {ChainBridge#buildUtxoHash} - */ - buildUtxoHash(utxo: UnspentTransactionOutput): Hex { - return Hex.from( - utils.solidityKeccak256( - ["bytes32", "uint32", "uint64"], - [ - utxo.transactionHash.reverse().toPrefixedString(), - utxo.outputIndex, - utxo.value, - ] - ) - ) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#getDepositRevealedEvents} - */ - async getRedemptionRequestedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - // FIXME: Filtering by indexed walletPubKeyHash field may not work - // until https://github.com/ethers-io/ethers.js/pull/4244 is - // included in the currently used version of ethers.js. - // Ultimately, we should upgrade ethers.js to include that fix. - // Short-term, we can workaround the problem as presented in: - // https://github.com/threshold-network/token-dashboard/blob/main/src/threshold-ts/tbtc/index.ts#L1041C1-L1093C1 - const events: EthersEvent[] = await this.getEvents( - "RedemptionRequested", - options, - ...filterArgs - ) - - return events.map((event) => { - const prefixedRedeemerOutputScript = Hex.from( - event.args!.redeemerOutputScript - ) - const redeemerOutputScript = prefixedRedeemerOutputScript - .toString() - .slice(readCompactSizeUint(prefixedRedeemerOutputScript).byteLength * 2) - - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(), - redeemer: new Address(event.args!.redeemer), - redeemerOutputScript: redeemerOutputScript, - requestedAmount: BigNumber.from(event.args!.requestedAmount), - treasuryFee: BigNumber.from(event.args!.treasuryFee), - txMaxFee: BigNumber.from(event.args!.txMaxFee), - } - }) - } -} - -/** - * Implementation of the Ethereum WalletRegistry handle. - * @see {ChainWalletRegistry} for reference. - */ -export class WalletRegistry - extends EthereumContract - implements ChainWalletRegistry -{ - constructor(config: ContractConfig) { - super(config, WalletRegistryDeployment) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainWalletRegistry#getWalletPublicKey} - */ - async getWalletPublicKey(walletID: Hex): Promise { - const publicKey = await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.getWalletPublicKey( - walletID.toPrefixedString() - ) - } - ) - return Hex.from(publicKey.substring(2)) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainWalletRegistry#getDkgResultSubmittedEvents} - */ - async getDkgResultSubmittedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events: EthersEvent[] = await this.getEvents( - "DkgResultSubmitted", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - resultHash: Hex.from(event.args!.resultHash), - seed: Hex.from(BigNumber.from(event.args!.seed).toHexString()), - result: { - submitterMemberIndex: BigNumber.from( - event.args!.result.submitterMemberIndex - ), - groupPubKey: Hex.from(event.args!.result.groupPubKey), - misbehavedMembersIndices: - event.args!.result.misbehavedMembersIndices.map((mmi: unknown) => - BigNumber.from(mmi).toNumber() - ), - signatures: Hex.from(event.args!.result.signatures), - signingMembersIndices: event.args!.result.signingMembersIndices.map( - BigNumber.from - ), - members: event.args!.result.members.map((m: unknown) => - BigNumber.from(m).toNumber() - ), - membersHash: Hex.from(event.args!.result.membersHash), - }, - } - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainWalletRegistry#getDkgResultApprovedEvents} - */ - async getDkgResultApprovedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events: EthersEvent[] = await this.getEvents( - "DkgResultApproved", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - resultHash: Hex.from(event.args!.resultHash), - approver: Address.from(event.args!.approver), - } - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainWalletRegistry#getDkgResultChallengedEvents} - */ - async getDkgResultChallengedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events: EthersEvent[] = await this.getEvents( - "DkgResultChallenged", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - resultHash: Hex.from(event.args!.resultHash), - challenger: Address.from(event.args!.challenger), - reason: event.args!.reason, - } - }) - } -} - -/** - * Implementation of the Ethereum TBTCVault handle. - */ -export class TBTCVault - extends EthereumContract - implements ChainTBTCVault -{ - constructor(config: ContractConfig) { - super(config, TBTCVaultDeployment) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#optimisticMintingDelay} - */ - async optimisticMintingDelay(): Promise { - const delaySeconds = await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.optimisticMintingDelay() - } - ) - - return BigNumber.from(delaySeconds).toNumber() - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#getMinters} - */ - async getMinters(): Promise { - const minters: string[] = await backoffRetrier( - this._totalRetryAttempts - )(async () => { - return await this._instance.getMinters() - }) - - return minters.map(Address.from) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#isMinter} - */ - async isMinter(address: Address): Promise { - return await backoffRetrier(this._totalRetryAttempts)(async () => { - return await this._instance.isMinter(`0x${address.identifierHex}`) - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#isGuardian} - */ - async isGuardian(address: Address): Promise { - return await backoffRetrier(this._totalRetryAttempts)(async () => { - return await this._instance.isGuardian(`0x${address.identifierHex}`) - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#requestOptimisticMint} - */ - async requestOptimisticMint( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise { - const tx = await sendWithRetry( - async () => { - return await this._instance.requestOptimisticMint( - depositTxHash.reverse().toPrefixedString(), - depositOutputIndex - ) - }, - this._totalRetryAttempts, - undefined, - [ - "Optimistic minting already requested for the deposit", - "The deposit is already swept", - ] - ) - - return Hex.from(tx.hash) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#cancelOptimisticMint} - */ - async cancelOptimisticMint( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise { - const tx = await sendWithRetry( - async () => { - return await this._instance.cancelOptimisticMint( - depositTxHash.reverse().toPrefixedString(), - depositOutputIndex - ) - }, - this._totalRetryAttempts, - undefined, - ["Optimistic minting already finalized for the deposit"] - ) - - return Hex.from(tx.hash) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#finalizeOptimisticMint} - */ - async finalizeOptimisticMint( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise { - const tx = await sendWithRetry( - async () => { - return await this._instance.finalizeOptimisticMint( - depositTxHash.reverse().toPrefixedString(), - depositOutputIndex - ) - }, - this._totalRetryAttempts, - undefined, - [ - "Optimistic minting already finalized for the deposit", - "The deposit is already swept", - ] - ) - - return Hex.from(tx.hash) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCVault#optimisticMintingRequests} - */ - async optimisticMintingRequests( - depositTxHash: TransactionHash, - depositOutputIndex: number - ): Promise { - const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) - - const request: ContractOptimisticMintingRequest = - await backoffRetrier( - this._totalRetryAttempts - )(async () => { - return await this._instance.optimisticMintingRequests(depositKey) - }) - return this.parseOptimisticMintingRequest(request) - } - - /** - * Parses a optimistic minting request using data fetched from the on-chain contract. - * @param request Data of the optimistic minting request. - * @returns Parsed optimistic minting request. - */ - private parseOptimisticMintingRequest( - request: ContractOptimisticMintingRequest - ): OptimisticMintingRequest { - return { - requestedAt: BigNumber.from(request.requestedAt).toNumber(), - finalizedAt: BigNumber.from(request.finalizedAt).toNumber(), - } - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#getOptimisticMintingRequestedEvents} - */ - async getOptimisticMintingRequestedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events = await this.getEvents( - "OptimisticMintingRequested", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - minter: new Address(event.args!.minter), - depositKey: Hex.from( - BigNumber.from(event.args!.depositKey).toHexString() - ), - depositor: new Address(event.args!.depositor), - amount: BigNumber.from(event.args!.amount), - fundingTxHash: TransactionHash.from( - event.args!.fundingTxHash - ).reverse(), - fundingOutputIndex: BigNumber.from( - event.args!.fundingOutputIndex - ).toNumber(), - } - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#getOptimisticMintingCancelledEvents} - */ - async getOptimisticMintingCancelledEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events = await this.getEvents( - "OptimisticMintingCancelled", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - guardian: new Address(event.args!.guardian), - depositKey: Hex.from( - BigNumber.from(event.args!.depositKey).toHexString() - ), - } - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainBridge#getOptimisticMintingFinalizedEvents} - */ - async getOptimisticMintingFinalizedEvents( - options?: GetEvents.Options, - ...filterArgs: Array - ): Promise { - const events = await this.getEvents( - "OptimisticMintingFinalized", - options, - ...filterArgs - ) - - return events.map((event) => { - return { - blockNumber: BigNumber.from(event.blockNumber).toNumber(), - blockHash: Hex.from(event.blockHash), - transactionHash: Hex.from(event.transactionHash), - minter: new Address(event.args!.minter), - depositKey: Hex.from( - BigNumber.from(event.args!.depositKey).toHexString() - ), - depositor: new Address(event.args!.depositor), - optimisticMintingDebt: BigNumber.from( - event.args!.optimisticMintingDebt - ), - } - }) - } -} - -/** - * Implementation of the Ethereum TBTC v2 token handle. - */ -export class TBTCToken - extends EthereumContract - implements ChainTBTCToken -{ - constructor(config: ContractConfig) { - super(config, TBTCDeployment) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCToken#totalSupply} - */ - async totalSupply(blockNumber?: number): Promise { - return this._instance.totalSupply({ - blockTag: blockNumber ?? "latest", - }) - } - - // eslint-disable-next-line valid-jsdoc - /** - * @see {ChainTBTCToken#requestRedemption} - */ - async requestRedemption( - walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, - redeemerOutputScript: string, - amount: BigNumber - ): Promise { - const redeemer = await this._instance?.signer?.getAddress() - if (!redeemer) { - throw new Error("Signer not provided") - } - - const vault = await this._instance.owner() - const extraData = this.buildRequestRedemptionData( - Address.from(redeemer), - walletPublicKey, - mainUtxo, - redeemerOutputScript - ) - - const tx = await sendWithRetry(async () => { - return await this._instance.approveAndCall( - vault, - amount, - extraData.toPrefixedString() - ) - }, this._totalRetryAttempts) - - return Hex.from(tx.hash) - } - - private buildRequestRedemptionData( - redeemer: Address, - walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, - redeemerOutputScript: string - ): Hex { - const { - walletPublicKeyHash, - prefixedRawRedeemerOutputScript, - mainUtxo: _mainUtxo, - } = this.buildBridgeRequestRedemptionData( - walletPublicKey, - mainUtxo, - redeemerOutputScript - ) - - return Hex.from( - utils.defaultAbiCoder.encode( - ["address", "bytes20", "bytes32", "uint32", "uint64", "bytes"], - [ - redeemer.identifierHex, - walletPublicKeyHash, - _mainUtxo.txHash, - _mainUtxo.txOutputIndex, - _mainUtxo.txOutputValue, - prefixedRawRedeemerOutputScript, - ] - ) - ) - } - - private buildBridgeRequestRedemptionData( - walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, - redeemerOutputScript: string - ) { - const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` - - const mainUtxoParam = { - // The Ethereum Bridge expects this hash to be in the Bitcoin internal - // byte order. - txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), - txOutputIndex: mainUtxo.outputIndex, - txOutputValue: mainUtxo.value, - } - - // Convert the output script to raw bytes buffer. - const rawRedeemerOutputScript = Buffer.from(redeemerOutputScript, "hex") - // Prefix the output script bytes buffer with 0x and its own length. - const prefixedRawRedeemerOutputScript = `0x${Buffer.concat([ - Buffer.from([rawRedeemerOutputScript.length]), - rawRedeemerOutputScript, - ]).toString("hex")}` - - return { - walletPublicKeyHash, - mainUtxo: mainUtxoParam, - prefixedRawRedeemerOutputScript, - } - } -} diff --git a/typescript/src/index.ts b/typescript/src/index.ts index a130de012..8fc6cb953 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -67,7 +67,7 @@ export { Address as EthereumAddress, TBTCVault as EthereumTBTCVault, TBTCToken as EthereumTBTCToken, -} from "./ethereum" +} from "./lib/ethereum" export { Hex } from "./hex" diff --git a/typescript/src/lib/ethereum/address.ts b/typescript/src/lib/ethereum/address.ts new file mode 100644 index 000000000..cd9f1a054 --- /dev/null +++ b/typescript/src/lib/ethereum/address.ts @@ -0,0 +1,32 @@ +import { Identifier as ChainIdentifier } from "../contracts" +import { utils } from "ethers" + +/** + * Represents an Ethereum address. + */ +// TODO: Make Address extends Hex +export class Address implements ChainIdentifier { + readonly identifierHex: string + + // TODO: Make constructor private + constructor(address: string) { + let validAddress: string + + try { + validAddress = utils.getAddress(address) + } catch (e) { + throw new Error(`Invalid Ethereum address`) + } + + this.identifierHex = validAddress.substring(2).toLowerCase() + } + + static from(address: string): Address { + return new Address(address) + } + + // TODO: Remove once extends Hex + equals(otherValue: Address): boolean { + return this.identifierHex === otherValue.identifierHex + } +} diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts new file mode 100644 index 000000000..3f444c7c7 --- /dev/null +++ b/typescript/src/lib/ethereum/bridge.ts @@ -0,0 +1,618 @@ +import { + Bridge as ContractBridge, + Deposit as ContractDeposit, + Redemption as ContractRedemption, + Wallets, +} from "../../../typechain/Bridge" +import { + Bridge as ChainBridge, + GetEvents, + Identifier as ChainIdentifier, + WalletRegistry as ChainWalletRegistry, +} from "../contracts" +import { + DepositRevealedEvent, + DepositScriptParameters, + RevealedDeposit, +} from "../../deposit" +import { Event as EthersEvent } from "@ethersproject/contracts" +import { BigNumber, constants, ContractTransaction, utils } from "ethers" +import { Hex } from "../../hex" +import { + compressPublicKey, + computeHash160, + DecomposedRawTransaction, + Proof, + readCompactSizeUint, + TransactionHash, + UnspentTransactionOutput, +} from "../bitcoin" +import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" +import { backoffRetrier } from "../../backoff" +import { NewWalletRegisteredEvent, Wallet, WalletState } from "../../wallet" +import { + ContractConfig, + EthereumContract, + sendWithRetry, +} from "./contract-handle" +import BridgeDeployment from "@keep-network/tbtc-v2/artifacts/Bridge.json" +import { Address } from "./address" +import { WalletRegistry } from "./wallet-registry" + +type ContractDepositRequest = ContractDeposit.DepositRequestStructOutput + +type ContractRedemptionRequest = + ContractRedemption.RedemptionRequestStructOutput + +/** + * Implementation of the Ethereum Bridge handle. + * @see {ChainBridge} for reference. + */ +export class Bridge + extends EthereumContract + implements ChainBridge +{ + constructor(config: ContractConfig) { + super(config, BridgeDeployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#getDepositRevealedEvents} + */ + async getDepositRevealedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events: EthersEvent[] = await this.getEvents( + "DepositRevealed", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + fundingTxHash: TransactionHash.from( + event.args!.fundingTxHash + ).reverse(), + fundingOutputIndex: BigNumber.from( + event.args!.fundingOutputIndex + ).toNumber(), + depositor: new Address(event.args!.depositor), + amount: BigNumber.from(event.args!.amount), + blindingFactor: Hex.from(event.args!.blindingFactor).toString(), + walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(), + refundPublicKeyHash: Hex.from(event.args!.refundPubKeyHash).toString(), + refundLocktime: Hex.from(event.args!.refundLocktime).toString(), + vault: + event.args!.vault === constants.AddressZero + ? undefined + : new Address(event.args!.vault), + } + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#pendingRedemptions} + */ + async pendingRedemptions( + walletPublicKey: string, + redeemerOutputScript: string + ): Promise { + const redemptionKey = Bridge.buildRedemptionKey( + computeHash160(walletPublicKey), + redeemerOutputScript + ) + + const request: ContractRedemptionRequest = + await backoffRetrier(this._totalRetryAttempts)( + async () => { + return await this._instance.pendingRedemptions(redemptionKey) + } + ) + + return this.parseRedemptionRequest(request, redeemerOutputScript) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#timedOutRedemptions} + */ + async timedOutRedemptions( + walletPublicKey: string, + redeemerOutputScript: string + ): Promise { + const redemptionKey = Bridge.buildRedemptionKey( + computeHash160(walletPublicKey), + redeemerOutputScript + ) + + const request: ContractRedemptionRequest = + await backoffRetrier(this._totalRetryAttempts)( + async () => { + return await this._instance.timedOutRedemptions(redemptionKey) + } + ) + + return this.parseRedemptionRequest(request, redeemerOutputScript) + } + + /** + * Builds a redemption key required to refer a redemption request. + * @param walletPublicKeyHash The wallet public key hash that identifies the + * pending redemption (along with the redeemer output script). Must be + * unprefixed. + * @param redeemerOutputScript The redeemer output script that identifies the + * pending redemption (along with the wallet public key hash). Must be + * un-prefixed and not prepended with length. + * @returns The redemption key. + */ + static buildRedemptionKey( + walletPublicKeyHash: string, + redeemerOutputScript: string + ): string { + // Convert the output script to raw bytes buffer. + const rawRedeemerOutputScript = Buffer.from(redeemerOutputScript, "hex") + // Prefix the output script bytes buffer with 0x and its own length. + const prefixedRawRedeemerOutputScript = `0x${Buffer.concat([ + Buffer.from([rawRedeemerOutputScript.length]), + rawRedeemerOutputScript, + ]).toString("hex")}` + // Build the redemption key by using the 0x-prefixed wallet PKH and + // prefixed output script. + return utils.solidityKeccak256( + ["bytes32", "bytes20"], + [ + utils.solidityKeccak256(["bytes"], [prefixedRawRedeemerOutputScript]), + `0x${walletPublicKeyHash}`, + ] + ) + } + + /** + * Parses a redemption request using data fetched from the on-chain contract. + * @param request Data of the request. + * @param redeemerOutputScript The redeemer output script that identifies the + * pending redemption (along with the wallet public key hash). Must be + * un-prefixed and not prepended with length. + * @returns Parsed redemption request. + */ + private parseRedemptionRequest( + request: ContractRedemptionRequest, + redeemerOutputScript: string + ): RedemptionRequest { + return { + redeemer: new Address(request.redeemer), + redeemerOutputScript: redeemerOutputScript, + requestedAmount: BigNumber.from(request.requestedAmount), + treasuryFee: BigNumber.from(request.treasuryFee), + txMaxFee: BigNumber.from(request.txMaxFee), + requestedAt: BigNumber.from(request.requestedAt).toNumber(), + } + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#revealDeposit} + */ + async revealDeposit( + depositTx: DecomposedRawTransaction, + depositOutputIndex: number, + deposit: DepositScriptParameters, + vault?: ChainIdentifier + ): Promise { + const depositTxParam = { + version: `0x${depositTx.version}`, + inputVector: `0x${depositTx.inputs}`, + outputVector: `0x${depositTx.outputs}`, + locktime: `0x${depositTx.locktime}`, + } + + const revealParam = { + fundingOutputIndex: depositOutputIndex, + blindingFactor: `0x${deposit.blindingFactor}`, + walletPubKeyHash: `0x${deposit.walletPublicKeyHash}`, + refundPubKeyHash: `0x${deposit.refundPublicKeyHash}`, + refundLocktime: `0x${deposit.refundLocktime}`, + vault: vault ? `0x${vault.identifierHex}` : constants.AddressZero, + } + + const tx = await sendWithRetry( + async () => { + return await this._instance.revealDeposit(depositTxParam, revealParam) + }, + this._totalRetryAttempts, + undefined, + ["Deposit already revealed"] + ) + + return tx.hash + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#submitDepositSweepProof} + */ + async submitDepositSweepProof( + sweepTx: DecomposedRawTransaction, + sweepProof: Proof, + mainUtxo: UnspentTransactionOutput, + vault?: ChainIdentifier + ): Promise { + const sweepTxParam = { + version: `0x${sweepTx.version}`, + inputVector: `0x${sweepTx.inputs}`, + outputVector: `0x${sweepTx.outputs}`, + locktime: `0x${sweepTx.locktime}`, + } + + const sweepProofParam = { + merkleProof: `0x${sweepProof.merkleProof}`, + txIndexInBlock: sweepProof.txIndexInBlock, + bitcoinHeaders: `0x${sweepProof.bitcoinHeaders}`, + } + + const mainUtxoParam = { + // The Ethereum Bridge expects this hash to be in the Bitcoin internal + // byte order. + txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), + txOutputIndex: mainUtxo.outputIndex, + txOutputValue: mainUtxo.value, + } + + const vaultParam = vault + ? `0x${vault.identifierHex}` + : constants.AddressZero + + await sendWithRetry(async () => { + return await this._instance.submitDepositSweepProof( + sweepTxParam, + sweepProofParam, + mainUtxoParam, + vaultParam + ) + }, this._totalRetryAttempts) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#txProofDifficultyFactor} + */ + async txProofDifficultyFactor(): Promise { + const txProofDifficultyFactor: BigNumber = await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.txProofDifficultyFactor() + }) + + return txProofDifficultyFactor.toNumber() + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#requestRedemption} + */ + async requestRedemption( + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string, + amount: BigNumber + ): Promise { + const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` + + const mainUtxoParam = { + // The Ethereum Bridge expects this hash to be in the Bitcoin internal + // byte order. + txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), + txOutputIndex: mainUtxo.outputIndex, + txOutputValue: mainUtxo.value, + } + + // Convert the output script to raw bytes buffer. + const rawRedeemerOutputScript = Buffer.from(redeemerOutputScript, "hex") + // Prefix the output script bytes buffer with 0x and its own length. + const prefixedRawRedeemerOutputScript = `0x${Buffer.concat([ + Buffer.from([rawRedeemerOutputScript.length]), + rawRedeemerOutputScript, + ]).toString("hex")}` + + await sendWithRetry(async () => { + return await this._instance.requestRedemption( + walletPublicKeyHash, + mainUtxoParam, + prefixedRawRedeemerOutputScript, + amount + ) + }, this._totalRetryAttempts) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#submitRedemptionProof} + */ + async submitRedemptionProof( + redemptionTx: DecomposedRawTransaction, + redemptionProof: Proof, + mainUtxo: UnspentTransactionOutput, + walletPublicKey: string + ): Promise { + const redemptionTxParam = { + version: `0x${redemptionTx.version}`, + inputVector: `0x${redemptionTx.inputs}`, + outputVector: `0x${redemptionTx.outputs}`, + locktime: `0x${redemptionTx.locktime}`, + } + + const redemptionProofParam = { + merkleProof: `0x${redemptionProof.merkleProof}`, + txIndexInBlock: redemptionProof.txIndexInBlock, + bitcoinHeaders: `0x${redemptionProof.bitcoinHeaders}`, + } + + const mainUtxoParam = { + // The Ethereum Bridge expects this hash to be in the Bitcoin internal + // byte order. + txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), + txOutputIndex: mainUtxo.outputIndex, + txOutputValue: mainUtxo.value, + } + + const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` + + await sendWithRetry(async () => { + return await this._instance.submitRedemptionProof( + redemptionTxParam, + redemptionProofParam, + mainUtxoParam, + walletPublicKeyHash + ) + }, this._totalRetryAttempts) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#deposits} + */ + async deposits( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise { + const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) + + const deposit: ContractDepositRequest = + await backoffRetrier(this._totalRetryAttempts)( + async () => { + return await this._instance.deposits(depositKey) + } + ) + + return this.parseRevealedDeposit(deposit) + } + + /** + * Builds the deposit key required to refer a revealed deposit. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Deposit key. + */ + static buildDepositKey( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): string { + const prefixedReversedDepositTxHash = depositTxHash + .reverse() + .toPrefixedString() + + return utils.solidityKeccak256( + ["bytes32", "uint32"], + [prefixedReversedDepositTxHash, depositOutputIndex] + ) + } + + /** + * Parses a revealed deposit using data fetched from the on-chain contract. + * @param deposit Data of the revealed deposit. + * @returns Parsed revealed deposit. + */ + private parseRevealedDeposit( + deposit: ContractDepositRequest + ): RevealedDeposit { + return { + depositor: new Address(deposit.depositor), + amount: BigNumber.from(deposit.amount), + vault: + deposit.vault === constants.AddressZero + ? undefined + : new Address(deposit.vault), + revealedAt: BigNumber.from(deposit.revealedAt).toNumber(), + sweptAt: BigNumber.from(deposit.sweptAt).toNumber(), + treasuryFee: BigNumber.from(deposit.treasuryFee), + } + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#activeWalletPublicKey} + */ + async activeWalletPublicKey(): Promise { + const activeWalletPublicKeyHash: string = await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.activeWalletPubKeyHash() + }) + + if ( + activeWalletPublicKeyHash === "0x0000000000000000000000000000000000000000" + ) { + // If there is no active wallet currently, return undefined. + return undefined + } + + const { walletPublicKey } = await this.wallets( + Hex.from(activeWalletPublicKeyHash) + ) + + return walletPublicKey.toString() + } + + private async getWalletCompressedPublicKey(ecdsaWalletID: Hex): Promise { + const walletRegistry = await this.walletRegistry() + const uncompressedPublicKey = await walletRegistry.getWalletPublicKey( + ecdsaWalletID + ) + + return Hex.from(compressPublicKey(uncompressedPublicKey)) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#getNewWalletRegisteredEvents} + */ + async getNewWalletRegisteredEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events: EthersEvent[] = await this.getEvents( + "NewWalletRegistered", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + ecdsaWalletID: Hex.from(event.args!.ecdsaWalletID), + walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash), + } + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#walletRegistry} + */ + async walletRegistry(): Promise { + const { ecdsaWalletRegistry } = await backoffRetrier<{ + ecdsaWalletRegistry: string + }>(this._totalRetryAttempts)(async () => { + return await this._instance.contractReferences() + }) + + return new WalletRegistry({ + address: ecdsaWalletRegistry, + signerOrProvider: this._instance.signer || this._instance.provider, + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#wallets} + */ + async wallets(walletPublicKeyHash: Hex): Promise { + const wallet = await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.wallets( + walletPublicKeyHash.toPrefixedString() + ) + }) + + return this.parseWalletDetails(wallet) + } + + /** + * Parses a wallet data using data fetched from the on-chain contract. + * @param wallet Data of the wallet. + * @returns Parsed wallet data. + */ + private async parseWalletDetails( + wallet: Wallets.WalletStructOutput + ): Promise { + const ecdsaWalletID = Hex.from(wallet.ecdsaWalletID) + + return { + ecdsaWalletID, + walletPublicKey: await this.getWalletCompressedPublicKey(ecdsaWalletID), + mainUtxoHash: Hex.from(wallet.mainUtxoHash), + pendingRedemptionsValue: wallet.pendingRedemptionsValue, + createdAt: wallet.createdAt, + movingFundsRequestedAt: wallet.movingFundsRequestedAt, + closingStartedAt: wallet.closingStartedAt, + pendingMovedFundsSweepRequestsCount: + wallet.pendingMovedFundsSweepRequestsCount, + state: WalletState.parse(wallet.state), + movingFundsTargetWalletsCommitmentHash: Hex.from( + wallet.movingFundsTargetWalletsCommitmentHash + ), + } + } + + // eslint-disable-next-line valid-jsdoc + /** + * Builds the UTXO hash based on the UTXO components. UTXO hash is computed as + * `keccak256(txHash | txOutputIndex | txOutputValue)`. + * + * @see {ChainBridge#buildUtxoHash} + */ + buildUtxoHash(utxo: UnspentTransactionOutput): Hex { + return Hex.from( + utils.solidityKeccak256( + ["bytes32", "uint32", "uint64"], + [ + utxo.transactionHash.reverse().toPrefixedString(), + utxo.outputIndex, + utxo.value, + ] + ) + ) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#getDepositRevealedEvents} + */ + async getRedemptionRequestedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + // FIXME: Filtering by indexed walletPubKeyHash field may not work + // until https://github.com/ethers-io/ethers.js/pull/4244 is + // included in the currently used version of ethers.js. + // Ultimately, we should upgrade ethers.js to include that fix. + // Short-term, we can workaround the problem as presented in: + // https://github.com/threshold-network/token-dashboard/blob/main/src/threshold-ts/tbtc/index.ts#L1041C1-L1093C1 + const events: EthersEvent[] = await this.getEvents( + "RedemptionRequested", + options, + ...filterArgs + ) + + return events.map((event) => { + const prefixedRedeemerOutputScript = Hex.from( + event.args!.redeemerOutputScript + ) + const redeemerOutputScript = prefixedRedeemerOutputScript + .toString() + .slice(readCompactSizeUint(prefixedRedeemerOutputScript).byteLength * 2) + + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(), + redeemer: new Address(event.args!.redeemer), + redeemerOutputScript: redeemerOutputScript, + requestedAmount: BigNumber.from(event.args!.requestedAmount), + treasuryFee: BigNumber.from(event.args!.treasuryFee), + txMaxFee: BigNumber.from(event.args!.txMaxFee), + } + }) + } +} diff --git a/typescript/src/ethereum-helpers.ts b/typescript/src/lib/ethereum/contract-handle.ts similarity index 56% rename from typescript/src/ethereum-helpers.ts rename to typescript/src/lib/ethereum/contract-handle.ts index 456016476..c5ba09813 100644 --- a/typescript/src/ethereum-helpers.ts +++ b/typescript/src/lib/ethereum/contract-handle.ts @@ -1,15 +1,145 @@ +import { providers, Signer, utils } from "ethers" import { Contract as EthersContract, Event as EthersEvent, EventFilter as EthersEventFilter, -} from "ethers" +} from "@ethersproject/contracts" +import { GetEvents } from "../contracts" import { backoffRetrier, - skipRetryWhenMatched, ExecutionLoggerFn, -} from "./backoff" + skipRetryWhenMatched, +} from "../../backoff" +import { Address } from "./address" -const GET_EVENTS_BLOCK_INTERVAL = 10_000 +/** + * Contract deployment artifact. + * @see [hardhat-deploy#Deployment](https://github.com/wighawag/hardhat-deploy/blob/0c969e9a27b4eeff9f5ccac7e19721ef2329eed2/types.ts#L358)} + */ +export interface Deployment { + /** + * Address of the deployed contract. + */ + address: string + /** + * Contract's ABI. + */ + abi: any[] + /** + * Deployment transaction receipt. + */ + receipt: { + /** + * Number of block in which the contract was deployed. + */ + blockNumber: number + } +} + +/** + * Represents a config set required to connect an Ethereum contract. + */ +export interface ContractConfig { + /** + * Address of the Ethereum contract as a 0x-prefixed hex string. + * Optional parameter, if not provided the value will be resolved from the + * contract artifact. + */ + address?: string + /** + * Signer - will return a Contract which will act on behalf of that signer. The signer will sign all contract transactions. + * Provider - will return a downgraded Contract which only has read-only access (i.e. constant calls) + */ + signerOrProvider: Signer | providers.Provider + /** + * Number of a block in which the contract was deployed. + * Optional parameter, if not provided the value will be resolved from the + * contract artifact. + */ + deployedAtBlockNumber?: number +} + +/** + * Deployed Ethereum contract + */ +export class EthereumContract { + /** + * Ethers instance of the deployed contract. + */ + protected readonly _instance: T + /** + * Number of a block within which the contract was deployed. Value is read from + * the contract deployment artifact. It can be overwritten by setting a + * {@link ContractConfig.deployedAtBlockNumber} property. + */ + protected readonly _deployedAtBlockNumber: number + /** + * Number of retries for ethereum requests. + */ + protected readonly _totalRetryAttempts: number + + /** + * @param config Configuration for contract instance initialization. + * @param deployment Contract Deployment artifact. + * @param totalRetryAttempts Number of retries for ethereum requests. + */ + constructor( + config: ContractConfig, + deployment: Deployment, + totalRetryAttempts = 3 + ) { + this._instance = new EthersContract( + config.address ?? utils.getAddress(deployment.address), + `${JSON.stringify(deployment.abi)}`, + config.signerOrProvider + ) as T + + this._deployedAtBlockNumber = + config.deployedAtBlockNumber ?? deployment.receipt.blockNumber + + this._totalRetryAttempts = totalRetryAttempts + } + + /** + * Get address of the contract instance. + * @returns Address of this contract instance. + */ + getAddress(): Address { + return Address.from(this._instance.address) + } + + /** + * Get events emitted by the Ethereum contract. + * It starts searching from provided block number. If the {@link GetEvents.Options#fromBlock} + * option is missing it looks for a contract's defined property + * {@link _deployedAtBlockNumber}. If the property is missing starts searching + * from block `0`. + * @param eventName Name of the event. + * @param options Options for events fetching. + * @param filterArgs Arguments for events filtering. + * @returns Array of found events. + */ + async getEvents( + eventName: string, + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + // TODO: Test if we need a workaround for querying events from big range in chunks, + // see: https://github.com/keep-network/tbtc-monitoring/blob/e169357d7b8c638d4eaf73d52aa8f53ee4aebc1d/src/lib/ethereum-helper.js#L44-L73 + return backoffRetrier( + options?.retries ?? this._totalRetryAttempts + )(async () => { + return await getEvents( + this._instance, + this._instance.filters[eventName](...filterArgs), + options?.fromBlock ?? this._deployedAtBlockNumber, + options?.toBlock, + options?.batchedQueryBlockInterval, + options?.logger + ) + }) + } +} /** * Sends ethereum transaction with retries. @@ -80,6 +210,8 @@ function resolveEthersError(err: unknown): unknown { return err } +const GET_EVENTS_BLOCK_INTERVAL = 10_000 + /** * Looks up all existing events defined by the {@link event} filter on * {@link sourceContract}, searching past blocks and then returning them. @@ -97,7 +229,7 @@ function resolveEthersError(err: unknown): unknown { * @returns A promise that will be fulfilled by the list of event objects once * they are found. */ -export async function getEvents( +async function getEvents( sourceContract: EthersContract, event: EthersEventFilter, fromBlock: number = 0, diff --git a/typescript/src/lib/ethereum/index.ts b/typescript/src/lib/ethereum/index.ts new file mode 100644 index 000000000..e6ac22fcf --- /dev/null +++ b/typescript/src/lib/ethereum/index.ts @@ -0,0 +1,10 @@ +export * from "./address" +export * from "./bridge" +export * from "./tbtc-token" +export * from "./tbtc-vault" +export * from "./wallet-registry" + +// The `contract-handle` module should not be re-exported directly as it +// contains low-level contract integration code. Re-export only components +// that are relevant for `lib/ethereum` clients. +export { ContractConfig } from "./contract-handle" diff --git a/typescript/src/lib/ethereum/tbtc-token.ts b/typescript/src/lib/ethereum/tbtc-token.ts new file mode 100644 index 000000000..37b63df31 --- /dev/null +++ b/typescript/src/lib/ethereum/tbtc-token.ts @@ -0,0 +1,129 @@ +import { TBTC as ContractTBTC } from "../../../typechain/TBTC" +import { TBTCToken as ChainTBTCToken } from "../contracts" +import { BigNumber, ContractTransaction, utils } from "ethers" +import { computeHash160, UnspentTransactionOutput } from "../bitcoin" +import { Hex } from "../../hex" +import { + ContractConfig, + EthereumContract, + sendWithRetry, +} from "./contract-handle" +import TBTCDeployment from "@keep-network/tbtc-v2/artifacts/TBTC.json" +import { Address } from "./address" + +/** + * Implementation of the Ethereum TBTC v2 token handle. + */ +export class TBTCToken + extends EthereumContract + implements ChainTBTCToken +{ + constructor(config: ContractConfig) { + super(config, TBTCDeployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCToken#totalSupply} + */ + async totalSupply(blockNumber?: number): Promise { + return this._instance.totalSupply({ + blockTag: blockNumber ?? "latest", + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCToken#requestRedemption} + */ + async requestRedemption( + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string, + amount: BigNumber + ): Promise { + const redeemer = await this._instance?.signer?.getAddress() + if (!redeemer) { + throw new Error("Signer not provided") + } + + const vault = await this._instance.owner() + const extraData = this.buildRequestRedemptionData( + Address.from(redeemer), + walletPublicKey, + mainUtxo, + redeemerOutputScript + ) + + const tx = await sendWithRetry(async () => { + return await this._instance.approveAndCall( + vault, + amount, + extraData.toPrefixedString() + ) + }, this._totalRetryAttempts) + + return Hex.from(tx.hash) + } + + private buildRequestRedemptionData( + redeemer: Address, + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string + ): Hex { + const { + walletPublicKeyHash, + prefixedRawRedeemerOutputScript, + mainUtxo: _mainUtxo, + } = this.buildBridgeRequestRedemptionData( + walletPublicKey, + mainUtxo, + redeemerOutputScript + ) + + return Hex.from( + utils.defaultAbiCoder.encode( + ["address", "bytes20", "bytes32", "uint32", "uint64", "bytes"], + [ + redeemer.identifierHex, + walletPublicKeyHash, + _mainUtxo.txHash, + _mainUtxo.txOutputIndex, + _mainUtxo.txOutputValue, + prefixedRawRedeemerOutputScript, + ] + ) + ) + } + + private buildBridgeRequestRedemptionData( + walletPublicKey: string, + mainUtxo: UnspentTransactionOutput, + redeemerOutputScript: string + ) { + const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` + + const mainUtxoParam = { + // The Ethereum Bridge expects this hash to be in the Bitcoin internal + // byte order. + txHash: mainUtxo.transactionHash.reverse().toPrefixedString(), + txOutputIndex: mainUtxo.outputIndex, + txOutputValue: mainUtxo.value, + } + + // Convert the output script to raw bytes buffer. + const rawRedeemerOutputScript = Buffer.from(redeemerOutputScript, "hex") + // Prefix the output script bytes buffer with 0x and its own length. + const prefixedRawRedeemerOutputScript = `0x${Buffer.concat([ + Buffer.from([rawRedeemerOutputScript.length]), + rawRedeemerOutputScript, + ]).toString("hex")}` + + return { + walletPublicKeyHash, + mainUtxo: mainUtxoParam, + prefixedRawRedeemerOutputScript, + } + } +} diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts new file mode 100644 index 000000000..bf5f8c5d5 --- /dev/null +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -0,0 +1,286 @@ +import { TBTCVault as ContractTBTCVault } from "../../../typechain/TBTCVault" +import { GetEvents, TBTCVault as ChainTBTCVault } from "../contracts" +import { backoffRetrier } from "../../backoff" +import { BigNumber, ContractTransaction } from "ethers" +import { TransactionHash } from "../bitcoin" +import { Hex } from "../../hex" +import { + OptimisticMintingCancelledEvent, + OptimisticMintingFinalizedEvent, + OptimisticMintingRequest, + OptimisticMintingRequestedEvent, +} from "../../optimistic-minting" +import TBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" +import { + ContractConfig, + EthereumContract, + sendWithRetry, +} from "./contract-handle" +import { Address } from "./address" +import { Bridge } from "./bridge" + +type ContractOptimisticMintingRequest = { + requestedAt: BigNumber + finalizedAt: BigNumber +} + +/** + * Implementation of the Ethereum TBTCVault handle. + */ +export class TBTCVault + extends EthereumContract + implements ChainTBTCVault +{ + constructor(config: ContractConfig) { + super(config, TBTCVaultDeployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#optimisticMintingDelay} + */ + async optimisticMintingDelay(): Promise { + const delaySeconds = await backoffRetrier(this._totalRetryAttempts)( + async () => { + return await this._instance.optimisticMintingDelay() + } + ) + + return BigNumber.from(delaySeconds).toNumber() + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#getMinters} + */ + async getMinters(): Promise { + const minters: string[] = await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.getMinters() + }) + + return minters.map(Address.from) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#isMinter} + */ + async isMinter(address: Address): Promise { + return await backoffRetrier(this._totalRetryAttempts)(async () => { + return await this._instance.isMinter(`0x${address.identifierHex}`) + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#isGuardian} + */ + async isGuardian(address: Address): Promise { + return await backoffRetrier(this._totalRetryAttempts)(async () => { + return await this._instance.isGuardian(`0x${address.identifierHex}`) + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#requestOptimisticMint} + */ + async requestOptimisticMint( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise { + const tx = await sendWithRetry( + async () => { + return await this._instance.requestOptimisticMint( + depositTxHash.reverse().toPrefixedString(), + depositOutputIndex + ) + }, + this._totalRetryAttempts, + undefined, + [ + "Optimistic minting already requested for the deposit", + "The deposit is already swept", + ] + ) + + return Hex.from(tx.hash) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#cancelOptimisticMint} + */ + async cancelOptimisticMint( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise { + const tx = await sendWithRetry( + async () => { + return await this._instance.cancelOptimisticMint( + depositTxHash.reverse().toPrefixedString(), + depositOutputIndex + ) + }, + this._totalRetryAttempts, + undefined, + ["Optimistic minting already finalized for the deposit"] + ) + + return Hex.from(tx.hash) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#finalizeOptimisticMint} + */ + async finalizeOptimisticMint( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise { + const tx = await sendWithRetry( + async () => { + return await this._instance.finalizeOptimisticMint( + depositTxHash.reverse().toPrefixedString(), + depositOutputIndex + ) + }, + this._totalRetryAttempts, + undefined, + [ + "Optimistic minting already finalized for the deposit", + "The deposit is already swept", + ] + ) + + return Hex.from(tx.hash) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainTBTCVault#optimisticMintingRequests} + */ + async optimisticMintingRequests( + depositTxHash: TransactionHash, + depositOutputIndex: number + ): Promise { + const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) + + const request: ContractOptimisticMintingRequest = + await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.optimisticMintingRequests(depositKey) + }) + return this.parseOptimisticMintingRequest(request) + } + + /** + * Parses a optimistic minting request using data fetched from the on-chain contract. + * @param request Data of the optimistic minting request. + * @returns Parsed optimistic minting request. + */ + private parseOptimisticMintingRequest( + request: ContractOptimisticMintingRequest + ): OptimisticMintingRequest { + return { + requestedAt: BigNumber.from(request.requestedAt).toNumber(), + finalizedAt: BigNumber.from(request.finalizedAt).toNumber(), + } + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#getOptimisticMintingRequestedEvents} + */ + async getOptimisticMintingRequestedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events = await this.getEvents( + "OptimisticMintingRequested", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + minter: new Address(event.args!.minter), + depositKey: Hex.from( + BigNumber.from(event.args!.depositKey).toHexString() + ), + depositor: new Address(event.args!.depositor), + amount: BigNumber.from(event.args!.amount), + fundingTxHash: TransactionHash.from( + event.args!.fundingTxHash + ).reverse(), + fundingOutputIndex: BigNumber.from( + event.args!.fundingOutputIndex + ).toNumber(), + } + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#getOptimisticMintingCancelledEvents} + */ + async getOptimisticMintingCancelledEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events = await this.getEvents( + "OptimisticMintingCancelled", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + guardian: new Address(event.args!.guardian), + depositKey: Hex.from( + BigNumber.from(event.args!.depositKey).toHexString() + ), + } + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainBridge#getOptimisticMintingFinalizedEvents} + */ + async getOptimisticMintingFinalizedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events = await this.getEvents( + "OptimisticMintingFinalized", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + minter: new Address(event.args!.minter), + depositKey: Hex.from( + BigNumber.from(event.args!.depositKey).toHexString() + ), + depositor: new Address(event.args!.depositor), + optimisticMintingDebt: BigNumber.from( + event.args!.optimisticMintingDebt + ), + } + }) + } +} diff --git a/typescript/src/lib/ethereum/wallet-registry.ts b/typescript/src/lib/ethereum/wallet-registry.ts new file mode 100644 index 000000000..92502fb18 --- /dev/null +++ b/typescript/src/lib/ethereum/wallet-registry.ts @@ -0,0 +1,136 @@ +import { WalletRegistry as ContractWalletRegistry } from "../../../typechain/WalletRegistry" +import { GetEvents, WalletRegistry as ChainWalletRegistry } from "../contracts" +import { Hex } from "../../hex" +import { backoffRetrier } from "../../backoff" +import { + DkgResultApprovedEvent, + DkgResultChallengedEvent, + DkgResultSubmittedEvent, +} from "../../wallet" +import { Event as EthersEvent } from "@ethersproject/contracts" +import { BigNumber } from "ethers" +import WalletRegistryDeployment from "@keep-network/ecdsa/artifacts/WalletRegistry.json" +import { ContractConfig, EthereumContract } from "./contract-handle" +import { Address } from "./address" + +/** + * Implementation of the Ethereum WalletRegistry handle. + * @see {ChainWalletRegistry} for reference. + */ +export class WalletRegistry + extends EthereumContract + implements ChainWalletRegistry +{ + constructor(config: ContractConfig) { + super(config, WalletRegistryDeployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainWalletRegistry#getWalletPublicKey} + */ + async getWalletPublicKey(walletID: Hex): Promise { + const publicKey = await backoffRetrier(this._totalRetryAttempts)( + async () => { + return await this._instance.getWalletPublicKey( + walletID.toPrefixedString() + ) + } + ) + return Hex.from(publicKey.substring(2)) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainWalletRegistry#getDkgResultSubmittedEvents} + */ + async getDkgResultSubmittedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events: EthersEvent[] = await this.getEvents( + "DkgResultSubmitted", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + resultHash: Hex.from(event.args!.resultHash), + seed: Hex.from(BigNumber.from(event.args!.seed).toHexString()), + result: { + submitterMemberIndex: BigNumber.from( + event.args!.result.submitterMemberIndex + ), + groupPubKey: Hex.from(event.args!.result.groupPubKey), + misbehavedMembersIndices: + event.args!.result.misbehavedMembersIndices.map((mmi: unknown) => + BigNumber.from(mmi).toNumber() + ), + signatures: Hex.from(event.args!.result.signatures), + signingMembersIndices: event.args!.result.signingMembersIndices.map( + BigNumber.from + ), + members: event.args!.result.members.map((m: unknown) => + BigNumber.from(m).toNumber() + ), + membersHash: Hex.from(event.args!.result.membersHash), + }, + } + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainWalletRegistry#getDkgResultApprovedEvents} + */ + async getDkgResultApprovedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events: EthersEvent[] = await this.getEvents( + "DkgResultApproved", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + resultHash: Hex.from(event.args!.resultHash), + approver: Address.from(event.args!.approver), + } + }) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {ChainWalletRegistry#getDkgResultChallengedEvents} + */ + async getDkgResultChallengedEvents( + options?: GetEvents.Options, + ...filterArgs: Array + ): Promise { + const events: EthersEvent[] = await this.getEvents( + "DkgResultChallenged", + options, + ...filterArgs + ) + + return events.map((event) => { + return { + blockNumber: BigNumber.from(event.blockNumber).toNumber(), + blockHash: Hex.from(event.blockHash), + transactionHash: Hex.from(event.transactionHash), + resultHash: Hex.from(event.args!.resultHash), + challenger: Address.from(event.args!.challenger), + reason: event.args!.reason, + } + }) + } +} diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index 880f6a896..ebb835187 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -5,7 +5,7 @@ import { TransactionHash, } from "../../src/lib/bitcoin" import { Deposit, calculateDepositRefundLocktime } from "../../src/deposit" -import { Address } from "../../src/ethereum" +import { Address } from "../../src/lib/ethereum" /** * Testnet private key that can be used to refund the deposits used in tests. diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index 1992cdb85..e567dce0f 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -9,7 +9,7 @@ import { } from "../../src/lib/bitcoin" import { calculateDepositRefundLocktime, Deposit } from "../../src/deposit" import { BigNumber } from "ethers" -import { Address } from "../../src/ethereum" +import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src" export const NO_MAIN_UTXO = { diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index d9e232445..46c695c9b 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -10,8 +10,8 @@ import { createOutputScriptFromAddress, } from "../../src/lib/bitcoin" import { RedemptionRequest } from "../../src/redemption" -import { Address } from "../../src/ethereum" -import { BitcoinNetwork, BitcoinTransaction, Hex } from "../../src" +import { Address } from "../../src/lib/ethereum" +import { BitcoinTransaction, Hex } from "../../src" import { WalletState } from "../../src/wallet" /** @@ -723,8 +723,7 @@ export const findWalletForRedemptionData: { outputIndex: 0, value: BigNumber.from("791613461"), scriptPubKey: createOutputScriptFromAddress( - "tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja", - BitcoinNetwork.Testnet + "tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja" ), }, ], @@ -849,8 +848,7 @@ export const findWalletForRedemptionData: { outputIndex: 0, value: BigNumber.from("3370000"), // 0.0337 BTC scriptPubKey: createOutputScriptFromAddress( - "tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh", - BitcoinNetwork.Testnet + "tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh" ), }, ], diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index fd94c1f04..bab39a487 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -14,6 +14,7 @@ import { UnspentTransactionOutput, } from "../src/lib/bitcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" +import bcoin from "bcoin" import { assembleDepositScript, assembleDepositTransaction, @@ -29,8 +30,7 @@ import { suggestDepositWallet, } from "../src/deposit" import { MockBridge } from "./utils/mock-bridge" -import { txToJSON } from "./utils/helpers" -import { Address } from "../src/ethereum" +import { Address } from "../src/lib/ethereum" import { BitcoinNetwork } from "../src" describe("Deposit", () => { @@ -224,8 +224,16 @@ describe("Deposit", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { + bcoin.set("testnet") + bitcoinClient = new MockBitcoinClient() + // Tie used testnetAddress with testnetUTXO to use it during deposit + // creation. + const utxos = new Map() + utxos.set(testnetAddress, [testnetUTXO]) + bitcoinClient.unspentTransactionOutputs = utxos + // Tie testnetTransaction to testnetUTXO. This is needed since // submitDepositTransaction attach transaction data to each UTXO. const rawTransactions = new Map() @@ -238,14 +246,11 @@ describe("Deposit", () => { let depositUtxo: UnspentTransactionOutput beforeEach(async () => { - const fee = BigNumber.from(1520) ;({ transactionHash, depositUtxo } = await submitDepositTransaction( deposit, testnetPrivateKey, bitcoinClient, - true, - [testnetUTXO], - fee + true )) }) @@ -278,15 +283,11 @@ describe("Deposit", () => { let depositUtxo: UnspentTransactionOutput beforeEach(async () => { - const fee = BigNumber.from(1410) - ;({ transactionHash, depositUtxo } = await submitDepositTransaction( deposit, testnetPrivateKey, bitcoinClient, - false, - [testnetUTXO], - fee + false )) }) @@ -322,18 +323,15 @@ describe("Deposit", () => { let transaction: RawTransaction beforeEach(async () => { - const fee = BigNumber.from(1520) ;({ transactionHash, depositUtxo, rawTransaction: transaction, } = await assembleDepositTransaction( - BitcoinNetwork.Testnet, deposit, - testnetPrivateKey, - true, [testnetUTXO], - fee + testnetPrivateKey, + true )) }) @@ -342,10 +340,8 @@ describe("Deposit", () => { expect(transaction).to.be.eql(expectedP2WSHDeposit.transaction) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( expectedP2WSHDeposit.transactionHash.toString() @@ -357,12 +353,15 @@ describe("Deposit", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal(testnetUTXO.transactionHash.toString()) - expect(input.index).to.be.equal(testnetUTXO.outputIndex) + expect(input.prevout.hash).to.be.equal( + testnetUTXO.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(testnetAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -421,18 +420,15 @@ describe("Deposit", () => { let transaction: RawTransaction beforeEach(async () => { - const fee = BigNumber.from(1410) ;({ transactionHash, depositUtxo, rawTransaction: transaction, } = await assembleDepositTransaction( - BitcoinNetwork.Testnet, deposit, - testnetPrivateKey, - false, [testnetUTXO], - fee + testnetPrivateKey, + false )) }) @@ -441,10 +437,8 @@ describe("Deposit", () => { expect(transaction).to.be.eql(expectedP2SHDeposit.transaction) // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( expectedP2SHDeposit.transactionHash.toString() @@ -456,12 +450,15 @@ describe("Deposit", () => { const input = txJSON.inputs[0] - expect(input.hash).to.be.equal(testnetUTXO.transactionHash.toString()) - expect(input.index).to.be.equal(testnetUTXO.outputIndex) + expect(input.prevout.hash).to.be.equal( + testnetUTXO.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(testnetAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -701,14 +698,11 @@ describe("Deposit", () => { beforeEach(async () => { // Create a deposit transaction. - const fee = BigNumber.from(1520) const result = await assembleDepositTransaction( - BitcoinNetwork.Testnet, deposit, - testnetPrivateKey, - true, [testnetUTXO], - fee + testnetPrivateKey, + true ) transaction = result.rawTransaction @@ -746,14 +740,11 @@ describe("Deposit", () => { beforeEach(async () => { // Create a deposit transaction. - const fee = BigNumber.from(1520) ;({ depositUtxo } = await assembleDepositTransaction( - BitcoinNetwork.Testnet, deposit, - testnetPrivateKey, - true, [testnetUTXO], - fee + testnetPrivateKey, + true )) revealedDeposit = { diff --git a/typescript/test/ethereum.test.ts b/typescript/test/ethereum.test.ts index 98215fc04..682ffd2a7 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/ethereum.test.ts @@ -1,4 +1,4 @@ -import { Address, Bridge, TBTCToken } from "../src/ethereum" +import { Address, Bridge, TBTCToken } from "../src/lib/ethereum" import { deployMockContract, MockContract, diff --git a/typescript/test/utils/mock-bitcoin-client.ts b/typescript/test/utils/mock-bitcoin-client.ts index b37dd7820..fdb0e17fb 100644 --- a/typescript/test/utils/mock-bitcoin-client.ts +++ b/typescript/test/utils/mock-bitcoin-client.ts @@ -1,12 +1,12 @@ -import { BitcoinNetwork } from "../../src/bitcoin-network" import { + BitcoinNetwork, Client, UnspentTransactionOutput, TransactionMerkleBranch, RawTransaction, Transaction, TransactionHash, -} from "../../src/bitcoin" +} from "../../src/lib/bitcoin" /** * Mock Bitcoin client used for test purposes. diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index c18c0909f..213bf4d44 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -1,9 +1,16 @@ -import { Bridge, WalletRegistry, GetEvents, Identifier } from "../../src/chain" +import { + Bridge, + WalletRegistry, + GetEvents, + Identifier, +} from "../../src/lib/contracts" import { DecomposedRawTransaction, Proof, UnspentTransactionOutput, -} from "../../src/bitcoin" + computeHash160, + TransactionHash, +} from "../../src/lib/bitcoin" import { BigNumberish, BigNumber, utils, constants } from "ethers" import { RedemptionRequest, RedemptionRequestedEvent } from "../redemption" import { @@ -11,9 +18,8 @@ import { DepositRevealedEvent, RevealedDeposit, } from "../../src/deposit" -import { computeHash160, TransactionHash } from "../../src/bitcoin" import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" -import { Address } from "../../src/ethereum" +import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src/hex" import { NewWalletRegisteredEvent, Wallet } from "../../src/wallet" diff --git a/typescript/test/utils/mock-tbtc-token.ts b/typescript/test/utils/mock-tbtc-token.ts index 2e37d3e68..5c4246085 100644 --- a/typescript/test/utils/mock-tbtc-token.ts +++ b/typescript/test/utils/mock-tbtc-token.ts @@ -1,7 +1,7 @@ -import { TBTCToken } from "../../src/chain" +import { TBTCToken } from "../../src/lib/contracts" import { Hex } from "../../src/hex" import { BigNumber } from "ethers" -import { UnspentTransactionOutput } from "../../src/bitcoin" +import { UnspentTransactionOutput } from "../../src/lib/bitcoin" interface RequestRedemptionLog { walletPublicKey: string From b3e99a10d8a5c678c333e420fde5525839ad1c84 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 25 Sep 2023 10:44:17 +0200 Subject: [PATCH 05/43] Transform `backoff.ts` and `hex.ts` files to `lib/utils` shared lib --- typescript/src/deposit.ts | 2 +- typescript/src/index.ts | 2 +- typescript/src/lib/bitcoin/address.ts | 2 +- typescript/src/lib/bitcoin/block.ts | 2 +- typescript/src/lib/bitcoin/csuint.ts | 2 +- typescript/src/lib/bitcoin/hash.ts | 2 +- typescript/src/lib/bitcoin/key.ts | 2 +- typescript/src/lib/bitcoin/network.ts | 2 +- typescript/src/lib/bitcoin/transaction.ts | 2 +- typescript/src/lib/contracts/bridge.ts | 2 +- typescript/src/lib/contracts/chain-event.ts | 3 +-- typescript/src/lib/contracts/tbtc-token.ts | 2 +- typescript/src/lib/contracts/tbtc-vault.ts | 2 +- typescript/src/lib/contracts/wallet-registry.ts | 2 +- typescript/src/lib/electrum/client.ts | 3 +-- typescript/src/lib/ethereum/bridge.ts | 3 +-- typescript/src/lib/ethereum/contract-handle.ts | 2 +- typescript/src/lib/ethereum/tbtc-token.ts | 2 +- typescript/src/lib/ethereum/tbtc-vault.ts | 3 +-- typescript/src/lib/ethereum/wallet-registry.ts | 3 +-- typescript/src/{ => lib/utils}/backoff.ts | 0 typescript/src/{ => lib/utils}/hex.ts | 0 typescript/src/lib/utils/index.ts | 2 ++ typescript/src/optimistic-minting.ts | 2 +- typescript/src/proof.ts | 2 +- typescript/src/redemption.ts | 2 +- typescript/src/wallet.ts | 2 +- typescript/test/bitcoin.test.ts | 2 +- typescript/test/hex.test.ts | 2 +- typescript/test/proof.test.ts | 2 +- typescript/test/utils/mock-bridge.ts | 2 +- typescript/test/utils/mock-tbtc-token.ts | 2 +- 32 files changed, 31 insertions(+), 34 deletions(-) rename typescript/src/{ => lib/utils}/backoff.ts (100%) rename typescript/src/{ => lib/utils}/hex.ts (100%) create mode 100644 typescript/src/lib/utils/index.ts diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index cf838bb34..59ba5b0f4 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -12,7 +12,7 @@ import { isPublicKeyHashLength, } from "./lib/bitcoin" import { Bridge, Event, Identifier } from "./lib/contracts" -import { Hex } from "./hex" +import { Hex } from "./lib/utils" const { opcodes } = bcoin.script.common diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 8fc6cb953..c340f622a 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -69,7 +69,7 @@ export { TBTCToken as EthereumTBTCToken, } from "./lib/ethereum" -export { Hex } from "./hex" +export { Hex } from "./lib/utils" export { OptimisticMintingRequest, diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts index 42bbbdd76..354cb3db7 100644 --- a/typescript/src/lib/bitcoin/address.ts +++ b/typescript/src/lib/bitcoin/address.ts @@ -1,5 +1,5 @@ import bcoin, { Script } from "bcoin" -import { Hex } from "../../hex" +import { Hex } from "../utils" import { BitcoinNetwork, toBcoinNetwork } from "./network" /** diff --git a/typescript/src/lib/bitcoin/block.ts b/typescript/src/lib/bitcoin/block.ts index 72b325a4e..6fc235d53 100644 --- a/typescript/src/lib/bitcoin/block.ts +++ b/typescript/src/lib/bitcoin/block.ts @@ -1,5 +1,5 @@ import { BigNumber } from "ethers" -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * BlockHeader represents the header of a Bitcoin block. For reference, see: diff --git a/typescript/src/lib/bitcoin/csuint.ts b/typescript/src/lib/bitcoin/csuint.ts index 42773d137..613736103 100644 --- a/typescript/src/lib/bitcoin/csuint.ts +++ b/typescript/src/lib/bitcoin/csuint.ts @@ -1,4 +1,4 @@ -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * Reads the leading compact size uint from the provided variable length data. diff --git a/typescript/src/lib/bitcoin/hash.ts b/typescript/src/lib/bitcoin/hash.ts index 34a0da163..bef166140 100644 --- a/typescript/src/lib/bitcoin/hash.ts +++ b/typescript/src/lib/bitcoin/hash.ts @@ -1,5 +1,5 @@ import { BigNumber, utils } from "ethers" -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * Computes the HASH160 for the given text. diff --git a/typescript/src/lib/bitcoin/key.ts b/typescript/src/lib/bitcoin/key.ts index 110991d81..968bd2bed 100644 --- a/typescript/src/lib/bitcoin/key.ts +++ b/typescript/src/lib/bitcoin/key.ts @@ -1,7 +1,7 @@ import bcoin from "bcoin" import wif from "wif" import { BigNumber } from "ethers" -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * Checks whether given public key is a compressed Bitcoin public key. diff --git a/typescript/src/lib/bitcoin/network.ts b/typescript/src/lib/bitcoin/network.ts index ce79fad2c..bdb810e44 100644 --- a/typescript/src/lib/bitcoin/network.ts +++ b/typescript/src/lib/bitcoin/network.ts @@ -1,4 +1,4 @@ -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * Bitcoin networks. diff --git a/typescript/src/lib/bitcoin/transaction.ts b/typescript/src/lib/bitcoin/transaction.ts index 00a7a03bd..7d9b69670 100644 --- a/typescript/src/lib/bitcoin/transaction.ts +++ b/typescript/src/lib/bitcoin/transaction.ts @@ -1,7 +1,7 @@ import { TX } from "bcoin" import bufio from "bufio" import { BigNumber } from "ethers" -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * Represents a transaction hash (or transaction ID) as an un-prefixed hex diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index 7452a69d4..67b95eba7 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -10,7 +10,7 @@ import { DepositScriptParameters, RevealedDeposit, } from "../../deposit" -import { Hex } from "../../hex" +import { Hex } from "../utils" import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" import { NewWalletRegisteredEvent, Wallet } from "../../wallet" import { GetEvents } from "./chain-event" diff --git a/typescript/src/lib/contracts/chain-event.ts b/typescript/src/lib/contracts/chain-event.ts index 09f5cb022..2d58b9ddd 100644 --- a/typescript/src/lib/contracts/chain-event.ts +++ b/typescript/src/lib/contracts/chain-event.ts @@ -1,5 +1,4 @@ -import { Hex } from "../../hex" -import { ExecutionLoggerFn } from "../../backoff" +import { ExecutionLoggerFn, Hex } from "../utils" /** * Represents a generic chain event. diff --git a/typescript/src/lib/contracts/tbtc-token.ts b/typescript/src/lib/contracts/tbtc-token.ts index 6133bb01d..dcfdd484a 100644 --- a/typescript/src/lib/contracts/tbtc-token.ts +++ b/typescript/src/lib/contracts/tbtc-token.ts @@ -1,6 +1,6 @@ import { BigNumber } from "ethers" import { UnspentTransactionOutput } from "../bitcoin" -import { Hex } from "../../hex" +import { Hex } from "../utils" /** * Interface for communication with the TBTC v2 token on-chain contract. diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts index 0ce82041f..0102225d5 100644 --- a/typescript/src/lib/contracts/tbtc-vault.ts +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -1,5 +1,5 @@ import { TransactionHash } from "../bitcoin" -import { Hex } from "../../hex" +import { Hex } from "../utils" import { OptimisticMintingCancelledEvent, OptimisticMintingFinalizedEvent, diff --git a/typescript/src/lib/contracts/wallet-registry.ts b/typescript/src/lib/contracts/wallet-registry.ts index ef0e1711b..d9a0626e7 100644 --- a/typescript/src/lib/contracts/wallet-registry.ts +++ b/typescript/src/lib/contracts/wallet-registry.ts @@ -1,4 +1,4 @@ -import { Hex } from "../../hex" +import { Hex } from "../utils" import { DkgResultApprovedEvent, DkgResultChallengedEvent, diff --git a/typescript/src/lib/electrum/client.ts b/typescript/src/lib/electrum/client.ts index f3d69cd7a..0142e8037 100644 --- a/typescript/src/lib/electrum/client.ts +++ b/typescript/src/lib/electrum/client.ts @@ -15,8 +15,7 @@ import { import Electrum from "electrum-client-js" import { BigNumber, utils } from "ethers" import { URL } from "url" -import { Hex } from "../../hex" -import { backoffRetrier, RetrierFn } from "../../backoff" +import { backoffRetrier, Hex, RetrierFn } from "../utils" /** * Represents a set of credentials required to establish an Electrum connection. diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 3f444c7c7..8415234eb 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -17,7 +17,7 @@ import { } from "../../deposit" import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber, constants, ContractTransaction, utils } from "ethers" -import { Hex } from "../../hex" +import { backoffRetrier, Hex } from "../utils" import { compressPublicKey, computeHash160, @@ -28,7 +28,6 @@ import { UnspentTransactionOutput, } from "../bitcoin" import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" -import { backoffRetrier } from "../../backoff" import { NewWalletRegisteredEvent, Wallet, WalletState } from "../../wallet" import { ContractConfig, diff --git a/typescript/src/lib/ethereum/contract-handle.ts b/typescript/src/lib/ethereum/contract-handle.ts index c5ba09813..12ad04230 100644 --- a/typescript/src/lib/ethereum/contract-handle.ts +++ b/typescript/src/lib/ethereum/contract-handle.ts @@ -9,7 +9,7 @@ import { backoffRetrier, ExecutionLoggerFn, skipRetryWhenMatched, -} from "../../backoff" +} from "../utils" import { Address } from "./address" /** diff --git a/typescript/src/lib/ethereum/tbtc-token.ts b/typescript/src/lib/ethereum/tbtc-token.ts index 37b63df31..51f1aa480 100644 --- a/typescript/src/lib/ethereum/tbtc-token.ts +++ b/typescript/src/lib/ethereum/tbtc-token.ts @@ -2,7 +2,7 @@ import { TBTC as ContractTBTC } from "../../../typechain/TBTC" import { TBTCToken as ChainTBTCToken } from "../contracts" import { BigNumber, ContractTransaction, utils } from "ethers" import { computeHash160, UnspentTransactionOutput } from "../bitcoin" -import { Hex } from "../../hex" +import { Hex } from "../utils" import { ContractConfig, EthereumContract, diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index bf5f8c5d5..c06b96217 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -1,9 +1,8 @@ import { TBTCVault as ContractTBTCVault } from "../../../typechain/TBTCVault" import { GetEvents, TBTCVault as ChainTBTCVault } from "../contracts" -import { backoffRetrier } from "../../backoff" import { BigNumber, ContractTransaction } from "ethers" import { TransactionHash } from "../bitcoin" -import { Hex } from "../../hex" +import { backoffRetrier, Hex } from "../utils" import { OptimisticMintingCancelledEvent, OptimisticMintingFinalizedEvent, diff --git a/typescript/src/lib/ethereum/wallet-registry.ts b/typescript/src/lib/ethereum/wallet-registry.ts index 92502fb18..0e6d8e3cb 100644 --- a/typescript/src/lib/ethereum/wallet-registry.ts +++ b/typescript/src/lib/ethereum/wallet-registry.ts @@ -1,7 +1,6 @@ import { WalletRegistry as ContractWalletRegistry } from "../../../typechain/WalletRegistry" import { GetEvents, WalletRegistry as ChainWalletRegistry } from "../contracts" -import { Hex } from "../../hex" -import { backoffRetrier } from "../../backoff" +import { backoffRetrier, Hex } from "../utils" import { DkgResultApprovedEvent, DkgResultChallengedEvent, diff --git a/typescript/src/backoff.ts b/typescript/src/lib/utils/backoff.ts similarity index 100% rename from typescript/src/backoff.ts rename to typescript/src/lib/utils/backoff.ts diff --git a/typescript/src/hex.ts b/typescript/src/lib/utils/hex.ts similarity index 100% rename from typescript/src/hex.ts rename to typescript/src/lib/utils/hex.ts diff --git a/typescript/src/lib/utils/index.ts b/typescript/src/lib/utils/index.ts new file mode 100644 index 000000000..d3e639c60 --- /dev/null +++ b/typescript/src/lib/utils/index.ts @@ -0,0 +1,2 @@ +export * from "./backoff" +export * from "./hex" diff --git a/typescript/src/optimistic-minting.ts b/typescript/src/optimistic-minting.ts index 04506cebf..b08141b86 100644 --- a/typescript/src/optimistic-minting.ts +++ b/typescript/src/optimistic-minting.ts @@ -1,7 +1,7 @@ import { BigNumber } from "ethers" import { TransactionHash } from "./lib/bitcoin" import { Identifier, Event, TBTCVault } from "./lib/contracts" -import { Hex } from "./hex" +import { Hex } from "./lib/utils" /** * Represents an event that is emitted when a new optimistic minting is requested diff --git a/typescript/src/proof.ts b/typescript/src/proof.ts index 7d7f739b7..5cddfcba0 100644 --- a/typescript/src/proof.ts +++ b/typescript/src/proof.ts @@ -13,7 +13,7 @@ import { BlockHeader, } from "./lib/bitcoin" import { BigNumber } from "ethers" -import { Hex } from "./hex" +import { Hex } from "./lib/utils" /** * Assembles a proof that a given transaction was included in the blockchain and diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index c5e9045d6..f22c5f718 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -12,7 +12,7 @@ import { import { Bridge, Event, Identifier, TBTCToken } from "./lib/contracts" import { assembleTransactionProof } from "./proof" import { determineWalletMainUtxo, WalletState } from "./wallet" -import { Hex } from "./hex" +import { Hex } from "./lib/utils" /** * Represents a redemption request. diff --git a/typescript/src/wallet.ts b/typescript/src/wallet.ts index a292b0574..cf0c5ae39 100644 --- a/typescript/src/wallet.ts +++ b/typescript/src/wallet.ts @@ -1,5 +1,5 @@ import { BigNumber } from "ethers" -import { Hex } from "./hex" +import { Hex } from "./lib/utils" import { Bridge, Event, Identifier } from "./lib/contracts" import { Client as BitcoinClient, diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index 0d5e71e38..dcaf31853 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -19,7 +19,7 @@ import { computeHash256, } from "../src/lib/bitcoin" import { calculateDepositRefundLocktime } from "../src/deposit" -import { Hex } from "../src/hex" +import { Hex } from "../src/lib/utils" import { BigNumber } from "ethers" import { btcAddresses } from "./data/bitcoin" diff --git a/typescript/test/hex.test.ts b/typescript/test/hex.test.ts index 08fdbfebb..1f8e538e3 100644 --- a/typescript/test/hex.test.ts +++ b/typescript/test/hex.test.ts @@ -1,5 +1,5 @@ import { assert } from "chai" -import { Hex } from "../src/hex" +import { Hex } from "../src/lib/utils" describe("Hex", () => { const stringUnprefixed = diff --git a/typescript/test/proof.test.ts b/typescript/test/proof.test.ts index ead678177..bfe846ac0 100644 --- a/typescript/test/proof.test.ts +++ b/typescript/test/proof.test.ts @@ -5,7 +5,7 @@ import { BlockHeader, Proof, } from "../src/lib/bitcoin" -import { Hex } from "../src/hex" +import { Hex } from "../src/lib/utils" import { singleInputProofTestData, multipleInputsProofTestData, diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index 213bf4d44..f1362f2a0 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -20,7 +20,7 @@ import { } from "../../src/deposit" import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" import { Address } from "../../src/lib/ethereum" -import { Hex } from "../../src/hex" +import { Hex } from "../../src/lib/utils" import { NewWalletRegisteredEvent, Wallet } from "../../src/wallet" interface DepositSweepProofLogEntry { diff --git a/typescript/test/utils/mock-tbtc-token.ts b/typescript/test/utils/mock-tbtc-token.ts index 5c4246085..588615404 100644 --- a/typescript/test/utils/mock-tbtc-token.ts +++ b/typescript/test/utils/mock-tbtc-token.ts @@ -1,5 +1,5 @@ import { TBTCToken } from "../../src/lib/contracts" -import { Hex } from "../../src/hex" +import { Hex } from "../../src/lib/utils" import { BigNumber } from "ethers" import { UnspentTransactionOutput } from "../../src/lib/bitcoin" From 0052915041d41c3cdca6b981cfa55393542c7c14 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 25 Sep 2023 11:42:04 +0200 Subject: [PATCH 06/43] Move chain types from `wallet.ts` to `lib/contracts` Those types are purely chain-related and should be placed next to the respective contracts interfaces. --- typescript/src/lib/contracts/bridge.ts | 116 +++++++++- .../src/lib/contracts/wallet-registry.ts | 104 ++++++++- typescript/src/lib/ethereum/bridge.ts | 4 +- .../src/lib/ethereum/wallet-registry.ts | 7 +- typescript/src/redemption.ts | 10 +- typescript/src/wallet.ts | 211 +----------------- typescript/test/utils/mock-bridge.ts | 3 +- typescript/test/wallet.test.ts | 3 +- 8 files changed, 232 insertions(+), 226 deletions(-) diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index 67b95eba7..0aef22d6c 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -12,8 +12,7 @@ import { } from "../../deposit" import { Hex } from "../utils" import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" -import { NewWalletRegisteredEvent, Wallet } from "../../wallet" -import { GetEvents } from "./chain-event" +import { Event, GetEvents } from "./chain-event" import { Identifier } from "./chain-identifier" import { WalletRegistry } from "./wallet-registry" @@ -182,3 +181,116 @@ export interface Bridge { */ getRedemptionRequestedEvents: GetEvents.Function } + +/* eslint-disable no-unused-vars */ +export enum WalletState { + /** + * The wallet is unknown to the Bridge. + */ + Unknown = 0, + /** + * The wallet can sweep deposits and accept redemption requests. + */ + Live = 1, + /** + * The wallet was deemed unhealthy and is expected to move their outstanding + * funds to another wallet. The wallet can still fulfill their pending redemption + * requests although new redemption requests and new deposit reveals are not + * accepted. + */ + MovingFunds = 2, + /** + * The wallet moved or redeemed all their funds and is in the + * losing period where it is still a subject of fraud challenges + * and must defend against them. + * */ + Closing = 3, + /** + * The wallet finalized the closing period successfully and can no longer perform + * any action in the Bridge. + * */ + Closed = 4, + /** + * The wallet committed a fraud that was reported, did not move funds to + * another wallet before a timeout, or did not sweep funds moved to if from + * another wallet before a timeout. The wallet is blocked and can not perform + * any actions in the Bridge. + */ + Terminated = 5, +} +/* eslint-enable no-unused-vars */ + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace WalletState { + export function parse(val: number): WalletState { + return ( + (WalletState)[ + Object.keys(WalletState)[ + Object.values(WalletState).indexOf(val as WalletState) + ] + ] ?? WalletState.Unknown + ) + } +} + +/** + * Represents a deposit. + */ +export interface Wallet { + /** + * Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry. + */ + ecdsaWalletID: Hex + /** + * Compressed public key of the ECDSA Wallet. + */ + walletPublicKey: Hex + /** + * Latest wallet's main UTXO hash. + */ + mainUtxoHash: Hex + /** + * The total redeemable value of pending redemption requests targeting that wallet. + */ + pendingRedemptionsValue: BigNumber + /** + * UNIX timestamp the wallet was created at. + */ + createdAt: number + /** + * UNIX timestamp indicating the moment the wallet was requested to move their + * funds. + */ + movingFundsRequestedAt: number + /** + * UNIX timestamp indicating the moment the wallet's closing period started. + */ + closingStartedAt: number + /** + * Total count of pending moved funds sweep requests targeting this wallet. + */ + pendingMovedFundsSweepRequestsCount: number + /** + * Current state of the wallet. + */ + state: WalletState + /** + * Moving funds target wallet commitment submitted by the wallet. + */ + movingFundsTargetWalletsCommitmentHash: Hex +} + +/** + * Represents an event emitted when new wallet is registered on the on-chain bridge. + */ +export type NewWalletRegisteredEvent = { + /** + * Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry. + */ + ecdsaWalletID: Hex + /** + * 20-byte public key hash of the ECDSA Wallet. It is computed by applying + * hash160 on the compressed public key of the ECDSA Wallet. + */ + walletPublicKeyHash: Hex +} & Event diff --git a/typescript/src/lib/contracts/wallet-registry.ts b/typescript/src/lib/contracts/wallet-registry.ts index d9a0626e7..969756a88 100644 --- a/typescript/src/lib/contracts/wallet-registry.ts +++ b/typescript/src/lib/contracts/wallet-registry.ts @@ -1,10 +1,7 @@ import { Hex } from "../utils" -import { - DkgResultApprovedEvent, - DkgResultChallengedEvent, - DkgResultSubmittedEvent, -} from "../../wallet" -import { GetEvents } from "./chain-event" +import { Event, GetEvents } from "./chain-event" +import { BigNumber } from "ethers" +import { Identifier } from "./chain-identifier" /** * Interface for communication with the WalletRegistry on-chain contract. @@ -35,3 +32,98 @@ export interface WalletRegistry { */ getDkgResultChallengedEvents: GetEvents.Function } + +/** + * Represents an event emitted when a DKG result is submitted to the on-chain + * wallet registry. + */ +export type DkgResultSubmittedEvent = { + /** + * 32-byte hash of the submitted DKG result. + */ + resultHash: Hex + /** + * 32-byte seed of the current DKG execution. + */ + seed: Hex + /** + * DKG result object. + */ + result: DkgResult +} & Event + +/** + * Represents an event emitted when a DKG result is approved on the on-chain + * wallet registry. + */ +export type DkgResultApprovedEvent = { + /** + * 32-byte hash of the submitted DKG result. + */ + resultHash: Hex + /** + * Approver's chain identifier. + */ + approver: Identifier +} & Event + +/** + * Represents an event emitted when a DKG result is challenged on the on-chain + * wallet registry. + */ +export type DkgResultChallengedEvent = { + /** + * 32-byte hash of the submitted DKG result. + */ + resultHash: Hex + /** + * Challenger's chain identifier. + */ + challenger: Identifier + /** + * Reason of the challenge. + */ + reason: string +} & Event + +/** + * Represents a DKG on-chain result. + */ +type DkgResult = { + /** + * Claimed submitter candidate group member index. Is in range [1, groupSize]. + */ + submitterMemberIndex: BigNumber + /** + * Generated group public key. + */ + groupPubKey: Hex + /** + * Array of misbehaved members indices (disqualified or inactive). Indices + * are in range [1, groupSize], unique, and sorted in ascending order. + */ + misbehavedMembersIndices: number[] + /** + * Concatenation of signatures from members supporting the result. + * The message to be signed by each member is keccak256 hash of the + * calculated group public key, misbehaved members indices and DKG + * start block. The calculated hash is also prefixed with + * `\x19Ethereum signed message:\n` before signing. + */ + signatures: Hex + /** + * Indices of members corresponding to each signature. Indices are + * in range [1, groupSize], unique, and sorted in ascending order. + */ + signingMembersIndices: BigNumber[] + /** + * Identifiers of candidate group members as outputted by the group + * selection protocol. + */ + members: number[] + /** + * Keccak256 hash of group members identifiers that actively took part + * in DKG (excluding IA/DQ members). + */ + membersHash: Hex +} diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 8415234eb..bd380a82e 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -9,6 +9,9 @@ import { GetEvents, Identifier as ChainIdentifier, WalletRegistry as ChainWalletRegistry, + NewWalletRegisteredEvent, + Wallet, + WalletState, } from "../contracts" import { DepositRevealedEvent, @@ -28,7 +31,6 @@ import { UnspentTransactionOutput, } from "../bitcoin" import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" -import { NewWalletRegisteredEvent, Wallet, WalletState } from "../../wallet" import { ContractConfig, EthereumContract, diff --git a/typescript/src/lib/ethereum/wallet-registry.ts b/typescript/src/lib/ethereum/wallet-registry.ts index 0e6d8e3cb..859a01661 100644 --- a/typescript/src/lib/ethereum/wallet-registry.ts +++ b/typescript/src/lib/ethereum/wallet-registry.ts @@ -1,11 +1,12 @@ import { WalletRegistry as ContractWalletRegistry } from "../../../typechain/WalletRegistry" -import { GetEvents, WalletRegistry as ChainWalletRegistry } from "../contracts" -import { backoffRetrier, Hex } from "../utils" import { + GetEvents, + WalletRegistry as ChainWalletRegistry, DkgResultApprovedEvent, DkgResultChallengedEvent, DkgResultSubmittedEvent, -} from "../../wallet" +} from "../contracts" +import { backoffRetrier, Hex } from "../utils" import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber } from "ethers" import WalletRegistryDeployment from "@keep-network/ecdsa/artifacts/WalletRegistry.json" diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index f22c5f718..f1c66d189 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -9,9 +9,15 @@ import { Client as BitcoinClient, TransactionHash, } from "./lib/bitcoin" -import { Bridge, Event, Identifier, TBTCToken } from "./lib/contracts" +import { + Bridge, + Event, + Identifier, + TBTCToken, + WalletState, +} from "./lib/contracts" import { assembleTransactionProof } from "./proof" -import { determineWalletMainUtxo, WalletState } from "./wallet" +import { determineWalletMainUtxo } from "./wallet" import { Hex } from "./lib/utils" /** diff --git a/typescript/src/wallet.ts b/typescript/src/wallet.ts index cf0c5ae39..6903042ff 100644 --- a/typescript/src/wallet.ts +++ b/typescript/src/wallet.ts @@ -1,6 +1,5 @@ -import { BigNumber } from "ethers" import { Hex } from "./lib/utils" -import { Bridge, Event, Identifier } from "./lib/contracts" +import { Bridge } from "./lib/contracts" import { Client as BitcoinClient, BitcoinNetwork, @@ -10,214 +9,6 @@ import { UnspentTransactionOutput, } from "./lib/bitcoin" -/* eslint-disable no-unused-vars */ -export enum WalletState { - /** - * The wallet is unknown to the Bridge. - */ - Unknown = 0, - /** - * The wallet can sweep deposits and accept redemption requests. - */ - Live = 1, - /** - * The wallet was deemed unhealthy and is expected to move their outstanding - * funds to another wallet. The wallet can still fulfill their pending redemption - * requests although new redemption requests and new deposit reveals are not - * accepted. - */ - MovingFunds = 2, - /** - * The wallet moved or redeemed all their funds and is in the - * losing period where it is still a subject of fraud challenges - * and must defend against them. - * */ - Closing = 3, - /** - * The wallet finalized the closing period successfully and can no longer perform - * any action in the Bridge. - * */ - Closed = 4, - /** - * The wallet committed a fraud that was reported, did not move funds to - * another wallet before a timeout, or did not sweep funds moved to if from - * another wallet before a timeout. The wallet is blocked and can not perform - * any actions in the Bridge. - */ - Terminated = 5, -} -/* eslint-enable no-unused-vars */ - -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace WalletState { - export function parse(val: number): WalletState { - return ( - (WalletState)[ - Object.keys(WalletState)[ - Object.values(WalletState).indexOf(val as WalletState) - ] - ] ?? WalletState.Unknown - ) - } -} - -/** - * Represents a deposit. - */ -export interface Wallet { - /** - * Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry. - */ - ecdsaWalletID: Hex - /** - * Compressed public key of the ECDSA Wallet. - */ - walletPublicKey: Hex - /** - * Latest wallet's main UTXO hash. - */ - mainUtxoHash: Hex - /** - * The total redeemable value of pending redemption requests targeting that wallet. - */ - pendingRedemptionsValue: BigNumber - /** - * UNIX timestamp the wallet was created at. - */ - createdAt: number - /** - * UNIX timestamp indicating the moment the wallet was requested to move their - * funds. - */ - movingFundsRequestedAt: number - /** - * UNIX timestamp indicating the moment the wallet's closing period started. - */ - closingStartedAt: number - /** - * Total count of pending moved funds sweep requests targeting this wallet. - */ - pendingMovedFundsSweepRequestsCount: number - /** - * Current state of the wallet. - */ - state: WalletState - /** - * Moving funds target wallet commitment submitted by the wallet. - */ - movingFundsTargetWalletsCommitmentHash: Hex -} - -/** - * Represents an event emitted when new wallet is registered on the on-chain bridge. - */ -export type NewWalletRegisteredEvent = { - /** - * Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry. - */ - ecdsaWalletID: Hex - /** - * 20-byte public key hash of the ECDSA Wallet. It is computed by applying - * hash160 on the compressed public key of the ECDSA Wallet. - */ - walletPublicKeyHash: Hex -} & Event - -/** - * Represents an event emitted when a DKG result is submitted to the on-chain - * wallet registry. - */ -export type DkgResultSubmittedEvent = { - /** - * 32-byte hash of the submitted DKG result. - */ - resultHash: Hex - /** - * 32-byte seed of the current DKG execution. - */ - seed: Hex - /** - * DKG result object. - */ - result: DkgResult -} & Event - -/** - * Represents an event emitted when a DKG result is approved on the on-chain - * wallet registry. - */ -export type DkgResultApprovedEvent = { - /** - * 32-byte hash of the submitted DKG result. - */ - resultHash: Hex - /** - * Approver's chain identifier. - */ - approver: Identifier -} & Event - -/** - * Represents an event emitted when a DKG result is challenged on the on-chain - * wallet registry. - */ -export type DkgResultChallengedEvent = { - /** - * 32-byte hash of the submitted DKG result. - */ - resultHash: Hex - /** - * Challenger's chain identifier. - */ - challenger: Identifier - /** - * Reason of the challenge. - */ - reason: string -} & Event - -/** - * Represents a DKG on-chain result. - */ -type DkgResult = { - /** - * Claimed submitter candidate group member index. Is in range [1, groupSize]. - */ - submitterMemberIndex: BigNumber - /** - * Generated group public key. - */ - groupPubKey: Hex - /** - * Array of misbehaved members indices (disqualified or inactive). Indices - * are in range [1, groupSize], unique, and sorted in ascending order. - */ - misbehavedMembersIndices: number[] - /** - * Concatenation of signatures from members supporting the result. - * The message to be signed by each member is keccak256 hash of the - * calculated group public key, misbehaved members indices and DKG - * start block. The calculated hash is also prefixed with - * `\x19Ethereum signed message:\n` before signing. - */ - signatures: Hex - /** - * Indices of members corresponding to each signature. Indices are - * in range [1, groupSize], unique, and sorted in ascending order. - */ - signingMembersIndices: BigNumber[] - /** - * Identifiers of candidate group members as outputted by the group - * selection protocol. - */ - members: number[] - /** - * Keccak256 hash of group members identifiers that actively took part - * in DKG (excluding IA/DQ members). - */ - membersHash: Hex -} - /** * Determines the plain-text wallet main UTXO currently registered in the * Bridge on-chain contract. The returned main UTXO can be undefined if the diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index f1362f2a0..7f2551676 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -3,6 +3,8 @@ import { WalletRegistry, GetEvents, Identifier, + NewWalletRegisteredEvent, + Wallet, } from "../../src/lib/contracts" import { DecomposedRawTransaction, @@ -21,7 +23,6 @@ import { import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src/lib/utils" -import { NewWalletRegisteredEvent, Wallet } from "../../src/wallet" interface DepositSweepProofLogEntry { sweepTx: DecomposedRawTransaction diff --git a/typescript/test/wallet.test.ts b/typescript/test/wallet.test.ts index 678c396c4..5e5f83997 100644 --- a/typescript/test/wallet.test.ts +++ b/typescript/test/wallet.test.ts @@ -1,9 +1,10 @@ import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { MockBridge } from "./utils/mock-bridge" import { BitcoinNetwork, BitcoinTransaction, Hex } from "../src" -import { determineWalletMainUtxo, Wallet } from "../src/wallet" +import { determineWalletMainUtxo } from "../src/wallet" import { expect } from "chai" import { encodeToBitcoinAddress } from "../src/lib/bitcoin" +import { Wallet } from "../src/lib/contracts" import { BigNumber } from "ethers" describe("Wallet", () => { From 3a1a92c77e646936fedf01dd977ff1c52f39b086 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 25 Sep 2023 11:51:51 +0200 Subject: [PATCH 07/43] Move chain types from `redemption.ts` to `lib/contracts` Those types are purely chain-related and should be placed next to the respective contracts interfaces. --- typescript/src/lib/contracts/bridge.ts | 57 ++++++++++++++++++++++++- typescript/src/lib/ethereum/bridge.ts | 3 +- typescript/src/redemption.ts | 59 +------------------------- typescript/test/data/redemption.ts | 3 +- typescript/test/ethereum.test.ts | 2 +- typescript/test/redemption.test.ts | 3 +- typescript/test/utils/mock-bridge.ts | 3 +- 7 files changed, 64 insertions(+), 66 deletions(-) diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index 0aef22d6c..53a3a0fea 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -11,7 +11,6 @@ import { RevealedDeposit, } from "../../deposit" import { Hex } from "../utils" -import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" import { Event, GetEvents } from "./chain-event" import { Identifier } from "./chain-identifier" import { WalletRegistry } from "./wallet-registry" @@ -182,6 +181,62 @@ export interface Bridge { getRedemptionRequestedEvents: GetEvents.Function } +/** + * Represents a redemption request. + */ +export interface RedemptionRequest { + /** + * On-chain identifier of the redeemer. + */ + redeemer: Identifier + + /** + * The output script the redeemed Bitcoin funds are locked to. It is un-prefixed + * and is not prepended with length. + */ + redeemerOutputScript: string + + /** + * The amount of Bitcoins in satoshis that is requested to be redeemed. + * The actual value of the output in the Bitcoin transaction will be decreased + * by the sum of the fee share and the treasury fee for this particular output. + */ + requestedAmount: BigNumber + + /** + * The amount of Bitcoins in satoshis that is subtracted from the amount of + * the redemption request and used to pay the treasury fee. + * The value should be exactly equal to the value of treasury fee in the Bridge + * on-chain contract at the time the redemption request was made. + */ + treasuryFee: BigNumber + + /** + * The maximum amount of Bitcoins in satoshis that can be subtracted from the + * redemption's `requestedAmount` to pay the transaction network fee. + */ + txMaxFee: BigNumber + + /** + * UNIX timestamp the request was created at. + */ + requestedAt: number +} + +/** + * Represents an event emitted on redemption request. + */ +export type RedemptionRequestedEvent = Omit< + RedemptionRequest, + "requestedAt" +> & { + /** + * Public key hash of the wallet that is meant to handle the redemption. Must + * be an unprefixed hex string (without 0x prefix). + */ + walletPublicKeyHash: string +} & Event + /* eslint-disable no-unused-vars */ export enum WalletState { /** diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index bd380a82e..0dc461d44 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -12,6 +12,8 @@ import { NewWalletRegisteredEvent, Wallet, WalletState, + RedemptionRequest, + RedemptionRequestedEvent, } from "../contracts" import { DepositRevealedEvent, @@ -30,7 +32,6 @@ import { TransactionHash, UnspentTransactionOutput, } from "../bitcoin" -import { RedemptionRequest, RedemptionRequestedEvent } from "../../redemption" import { ContractConfig, EthereumContract, diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index f1c66d189..e1a3f0528 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -11,8 +11,7 @@ import { } from "./lib/bitcoin" import { Bridge, - Event, - Identifier, + RedemptionRequest, TBTCToken, WalletState, } from "./lib/contracts" @@ -20,62 +19,6 @@ import { assembleTransactionProof } from "./proof" import { determineWalletMainUtxo } from "./wallet" import { Hex } from "./lib/utils" -/** - * Represents a redemption request. - */ -export interface RedemptionRequest { - /** - * On-chain identifier of the redeemer. - */ - redeemer: Identifier - - /** - * The output script the redeemed Bitcoin funds are locked to. It is un-prefixed - * and is not prepended with length. - */ - redeemerOutputScript: string - - /** - * The amount of Bitcoins in satoshis that is requested to be redeemed. - * The actual value of the output in the Bitcoin transaction will be decreased - * by the sum of the fee share and the treasury fee for this particular output. - */ - requestedAmount: BigNumber - - /** - * The amount of Bitcoins in satoshis that is subtracted from the amount of - * the redemption request and used to pay the treasury fee. - * The value should be exactly equal to the value of treasury fee in the Bridge - * on-chain contract at the time the redemption request was made. - */ - treasuryFee: BigNumber - - /** - * The maximum amount of Bitcoins in satoshis that can be subtracted from the - * redemption's `requestedAmount` to pay the transaction network fee. - */ - txMaxFee: BigNumber - - /** - * UNIX timestamp the request was created at. - */ - requestedAt: number -} - -/** - * Represents an event emitted on redemption request. - */ -export type RedemptionRequestedEvent = Omit< - RedemptionRequest, - "requestedAt" -> & { - /** - * Public key hash of the wallet that is meant to handle the redemption. Must - * be an unprefixed hex string (without 0x prefix). - */ - walletPublicKeyHash: string -} & Event - /** * Requests a redemption of tBTC into BTC. * @param walletPublicKey - The Bitcoin public key of the wallet. Must be in the diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index 46c695c9b..3ba8b0b57 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -9,10 +9,9 @@ import { TransactionHash, createOutputScriptFromAddress, } from "../../src/lib/bitcoin" -import { RedemptionRequest } from "../../src/redemption" +import { RedemptionRequest, WalletState } from "../../src/lib/contracts" import { Address } from "../../src/lib/ethereum" import { BitcoinTransaction, Hex } from "../../src" -import { WalletState } from "../../src/wallet" /** * Private key (testnet) of the wallet. diff --git a/typescript/test/ethereum.test.ts b/typescript/test/ethereum.test.ts index 682ffd2a7..ae98d3f9a 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/ethereum.test.ts @@ -11,7 +11,7 @@ import { abi as WalletRegistryABI } from "@keep-network/ecdsa/artifacts/WalletRe import { MockProvider } from "@ethereum-waffle/provider" import { waffleChai } from "@ethereum-waffle/chai" import { TransactionHash, computeHash160 } from "../src/lib/bitcoin" -import { Hex } from "../src/hex" +import { Hex } from "../src/lib/utils" chai.use(waffleChai) diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index b83ad3161..ee59f3b48 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -23,11 +23,11 @@ import { RedemptionTestData, findWalletForRedemptionData, } from "./data/redemption" +import { RedemptionRequest, Wallet } from "../src/lib/contracts" import { assembleRedemptionTransaction, findWalletForRedemption, getRedemptionRequest, - RedemptionRequest, requestRedemption, submitRedemptionProof, submitRedemptionTransaction, @@ -37,7 +37,6 @@ import * as chai from "chai" import chaiAsPromised from "chai-as-promised" import { expect } from "chai" import { BigNumberish, BigNumber } from "ethers" -import { Wallet } from "../src/wallet" import { MockTBTCToken } from "./utils/mock-tbtc-token" import { BitcoinTransaction } from "../src" diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index 7f2551676..30ee58aff 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -5,6 +5,8 @@ import { Identifier, NewWalletRegisteredEvent, Wallet, + RedemptionRequest, + RedemptionRequestedEvent, } from "../../src/lib/contracts" import { DecomposedRawTransaction, @@ -14,7 +16,6 @@ import { TransactionHash, } from "../../src/lib/bitcoin" import { BigNumberish, BigNumber, utils, constants } from "ethers" -import { RedemptionRequest, RedemptionRequestedEvent } from "../redemption" import { Deposit, DepositRevealedEvent, From b3390d8c7ebe07dcc48bdcce0fac5c82ebccbf68 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 25 Sep 2023 11:57:48 +0200 Subject: [PATCH 08/43] Move chain types from `optimistic-minting.ts` to `lib/contracts` Those types are purely chain-related and should be placed next to the respective contracts interfaces. --- typescript/src/index.ts | 2 +- typescript/src/lib/contracts/tbtc-vault.ts | 105 +++++++++++++++++++-- typescript/src/lib/ethereum/tbtc-vault.ts | 11 ++- typescript/src/optimistic-minting.ts | 99 +------------------ 4 files changed, 106 insertions(+), 111 deletions(-) diff --git a/typescript/src/index.ts b/typescript/src/index.ts index c340f622a..66c92731e 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -74,4 +74,4 @@ export { Hex } from "./lib/utils" export { OptimisticMintingRequest, OptimisticMintingRequestedEvent, -} from "./optimistic-minting" +} from "./lib/contracts" diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts index 0102225d5..732251000 100644 --- a/typescript/src/lib/contracts/tbtc-vault.ts +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -1,13 +1,8 @@ import { TransactionHash } from "../bitcoin" import { Hex } from "../utils" -import { - OptimisticMintingCancelledEvent, - OptimisticMintingFinalizedEvent, - OptimisticMintingRequest, - OptimisticMintingRequestedEvent, -} from "../../optimistic-minting" import { Identifier } from "./chain-identifier" -import { GetEvents } from "./chain-event" +import { Event, GetEvents } from "./chain-event" +import { BigNumber } from "ethers" /** * Interface for communication with the TBTCVault on-chain contract. @@ -112,3 +107,99 @@ export interface TBTCVault { */ getOptimisticMintingFinalizedEvents: GetEvents.Function } + +/** + * Represents optimistic minting request for the given deposit revealed to the + * Bridge. + */ +export type OptimisticMintingRequest = { + /** + * UNIX timestamp at which the optimistic minting was requested. + */ + requestedAt: number + /** + * UNIX timestamp at which the optimistic minting was finalized. + * 0 if not yet finalized. + */ + finalizedAt: number +} + +/** + * Represents an event that is emitted when a new optimistic minting is requested + * on chain. + */ +export type OptimisticMintingRequestedEvent = { + /** + * Minter's chain identifier. + */ + minter: Identifier + /** + * Unique deposit identifier. + * @see Bridge.buildDepositKey + */ + depositKey: Hex + /** + * Depositor's chain identifier. + */ + depositor: Identifier + /** + * Amount of tokens requested to mint. + * This value is in ERC 1e18 precision, it has to be converted before using + * as Bitcoin value with 1e8 precision in satoshi. + */ + // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 + // precision to Bitcoin in 1e8 precision (satoshi). + amount: BigNumber + /** + * Hash of a Bitcoin transaction made to fund the deposit. + */ + fundingTxHash: TransactionHash + /** + * Index of an output in the funding transaction made to fund the deposit. + */ + fundingOutputIndex: number +} & Event + +/** + * Represents an event that is emitted when an optimistic minting request + * is cancelled on chain. + */ +export type OptimisticMintingCancelledEvent = { + /** + * Guardian's chain identifier. + */ + guardian: Identifier + /** + * Unique deposit identifier. + * @see Bridge.buildDepositKey + */ + depositKey: Hex +} & Event + +/** + * Represents an event that is emitted when an optimistic minting request + * is finalized on chain. + */ +export type OptimisticMintingFinalizedEvent = { + /** + * Minter's chain identifier. + */ + minter: Identifier + /** + * Unique deposit identifier. + * @see Bridge.buildDepositKey + */ + depositKey: Hex + /** + * Depositor's chain identifier. + */ + depositor: Identifier + /** + * Value of the new optimistic minting debt of the depositor. + * This value is in ERC 1e18 precision, it has to be converted before using + * as Bitcoin value with 1e8 precision in satoshi. + */ + // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 + // precision to Bitcoin in 1e8 precision (satoshi). + optimisticMintingDebt: BigNumber +} & Event diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index c06b96217..d381c2973 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -1,14 +1,15 @@ import { TBTCVault as ContractTBTCVault } from "../../../typechain/TBTCVault" -import { GetEvents, TBTCVault as ChainTBTCVault } from "../contracts" -import { BigNumber, ContractTransaction } from "ethers" -import { TransactionHash } from "../bitcoin" -import { backoffRetrier, Hex } from "../utils" import { + GetEvents, + TBTCVault as ChainTBTCVault, OptimisticMintingCancelledEvent, OptimisticMintingFinalizedEvent, OptimisticMintingRequest, OptimisticMintingRequestedEvent, -} from "../../optimistic-minting" +} from "../contracts" +import { BigNumber, ContractTransaction } from "ethers" +import { TransactionHash } from "../bitcoin" +import { backoffRetrier, Hex } from "../utils" import TBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import { ContractConfig, diff --git a/typescript/src/optimistic-minting.ts b/typescript/src/optimistic-minting.ts index b08141b86..d856b7b0e 100644 --- a/typescript/src/optimistic-minting.ts +++ b/typescript/src/optimistic-minting.ts @@ -1,104 +1,7 @@ -import { BigNumber } from "ethers" import { TransactionHash } from "./lib/bitcoin" -import { Identifier, Event, TBTCVault } from "./lib/contracts" +import { TBTCVault, OptimisticMintingRequest } from "./lib/contracts" import { Hex } from "./lib/utils" -/** - * Represents an event that is emitted when a new optimistic minting is requested - * on chain. - */ -export type OptimisticMintingRequestedEvent = { - /** - * Minter's chain identifier. - */ - minter: Identifier - /** - * Unique deposit identifier. - * @see Bridge.buildDepositKey - */ - depositKey: Hex - /** - * Depositor's chain identifier. - */ - depositor: Identifier - /** - * Amount of tokens requested to mint. - * This value is in ERC 1e18 precision, it has to be converted before using - * as Bitcoin value with 1e8 precision in satoshi. - */ - // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 - // precision to Bitcoin in 1e8 precision (satoshi). - amount: BigNumber - /** - * Hash of a Bitcoin transaction made to fund the deposit. - */ - fundingTxHash: TransactionHash - /** - * Index of an output in the funding transaction made to fund the deposit. - */ - fundingOutputIndex: number -} & Event - -/** - * Represents an event that is emitted when an optimistic minting request - * is cancelled on chain. - */ -export type OptimisticMintingCancelledEvent = { - /** - * Guardian's chain identifier. - */ - guardian: Identifier - /** - * Unique deposit identifier. - * @see Bridge.buildDepositKey - */ - depositKey: Hex -} & Event - -/** - * Represents an event that is emitted when an optimistic minting request - * is finalized on chain. - */ -export type OptimisticMintingFinalizedEvent = { - /** - * Minter's chain identifier. - */ - minter: Identifier - /** - * Unique deposit identifier. - * @see Bridge.buildDepositKey - */ - depositKey: Hex - /** - * Depositor's chain identifier. - */ - depositor: Identifier - /** - * Value of the new optimistic minting debt of the depositor. - * This value is in ERC 1e18 precision, it has to be converted before using - * as Bitcoin value with 1e8 precision in satoshi. - */ - // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 - // precision to Bitcoin in 1e8 precision (satoshi). - optimisticMintingDebt: BigNumber -} & Event - -/** - * Represents optimistic minting request for the given deposit revealed to the - * Bridge. - */ -export type OptimisticMintingRequest = { - /** - * UNIX timestamp at which the optimistic minting was requested. - */ - requestedAt: number - /** - * UNIX timestamp at which the optimistic minting was finalized. - * 0 if not yet finalized. - */ - finalizedAt: number -} - /** * Requests optimistic minting for a deposit on chain. * @param depositTxHash The revealed deposit transaction's hash. From dcba2dfded84d028a6ce8e1e6807b4d32799fb72 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 25 Sep 2023 12:38:44 +0200 Subject: [PATCH 09/43] Move chain types from `deposit.ts` to `lib/contracts` Those types are purely chain-related and should be placed next to the respective contracts interfaces. --- typescript/src/deposit-refund.ts | 2 +- typescript/src/deposit-sweep.ts | 4 +- typescript/src/deposit.ts | 104 ++----------------------- typescript/src/lib/contracts/bridge.ts | 101 ++++++++++++++++++++++-- typescript/src/lib/ethereum/bridge.ts | 4 +- typescript/test/data/deposit-refund.ts | 3 +- typescript/test/data/deposit-sweep.ts | 3 +- typescript/test/deposit.test.ts | 8 +- typescript/test/utils/mock-bridge.ts | 8 +- 9 files changed, 119 insertions(+), 118 deletions(-) diff --git a/typescript/src/deposit-refund.ts b/typescript/src/deposit-refund.ts index ec34038a1..16fa8448d 100644 --- a/typescript/src/deposit-refund.ts +++ b/typescript/src/deposit-refund.ts @@ -9,9 +9,9 @@ import { computeHash160, isCompressedPublicKey, } from "./lib/bitcoin" +import { Deposit } from "./lib/contracts" import { assembleDepositScript, - Deposit, validateDepositScriptParameters, } from "./deposit" diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 5f8e922be..6b8b2344d 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -10,8 +10,8 @@ import { TransactionHash, computeHash160, } from "./lib/bitcoin" -import { assembleDepositScript, Deposit } from "./deposit" -import { Bridge, Identifier } from "./lib/contracts" +import { assembleDepositScript } from "./deposit" +import { Bridge, Identifier, Deposit } from "./lib/contracts" import { assembleTransactionProof } from "./proof" /** diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index 59ba5b0f4..7d66bc7e8 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -11,107 +11,17 @@ import { TransactionHash, isPublicKeyHashLength, } from "./lib/bitcoin" -import { Bridge, Event, Identifier } from "./lib/contracts" +import { + Bridge, + Identifier, + Deposit, + DepositScriptParameters, + RevealedDeposit, +} from "./lib/contracts" import { Hex } from "./lib/utils" const { opcodes } = bcoin.script.common -// TODO: Replace all properties that are expected to be un-prefixed hexadecimal -// strings with a Hex type. - -/** - * Represents a deposit. - */ -export interface Deposit { - /** - * Depositor's chain identifier. - */ - depositor: Identifier - - /** - * Deposit amount in satoshis. - */ - amount: BigNumber - - /** - * An 8-byte blinding factor as an un-prefixed hex string. Must be unique - * for the given depositor, wallet public key and refund public key. - */ - blindingFactor: string - - /** - * Public key hash of the wallet that is meant to receive the deposit. Must - * be an unprefixed hex string (without 0x prefix). - * - * You can use `computeHash160` function to get the hash from a plain text public key. - */ - walletPublicKeyHash: string - - /** - * Public key hash that is meant to be used during deposit refund after the - * locktime passes. Must be an unprefixed hex string (without 0x prefix). - * - * You can use `computeHash160` function to get the hash from a plain text public key. - */ - refundPublicKeyHash: string - - /** - * A 4-byte little-endian refund locktime as an un-prefixed hex string. - */ - refundLocktime: string - - /** - * Optional identifier of the vault the deposit should be routed in. - */ - vault?: Identifier -} - -/** - * Helper type that groups deposit's fields required to assemble a deposit - * script. - */ -export type DepositScriptParameters = Pick< - Deposit, - | "depositor" - | "blindingFactor" - | "refundLocktime" - | "walletPublicKeyHash" - | "refundPublicKeyHash" -> & {} - -/** - * Represents a deposit revealed to the on-chain bridge. This type emphasizes - * the on-chain state of the revealed deposit and omits the deposit script - * parameters as they are not relevant in this context. - */ -export type RevealedDeposit = Pick< - Deposit, - "depositor" | "amount" | "vault" -> & { - /** - * UNIX timestamp the deposit was revealed at. - */ - revealedAt: number - /** - * UNIX timestamp the request was swept at. If not swept yet, this parameter - * should have zero as value. - */ - sweptAt: number - /** - * Value of the treasury fee calculated for this revealed deposit. - * Denominated in satoshi. - */ - treasuryFee: BigNumber -} - -/** - * Represents an event emitted on deposit reveal to the on-chain bridge. - */ -export type DepositRevealedEvent = Deposit & { - fundingTxHash: TransactionHash - fundingOutputIndex: number -} & Event - /** * Submits a deposit by creating and broadcasting a Bitcoin P2(W)SH * deposit transaction. diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index 53a3a0fea..c663dc527 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -5,11 +5,6 @@ import { DecomposedRawTransaction, TransactionHash, } from "../bitcoin" -import { - DepositRevealedEvent, - DepositScriptParameters, - RevealedDeposit, -} from "../../deposit" import { Hex } from "../utils" import { Event, GetEvents } from "./chain-event" import { Identifier } from "./chain-identifier" @@ -181,6 +176,102 @@ export interface Bridge { getRedemptionRequestedEvents: GetEvents.Function } +// TODO: Replace all properties that are expected to be un-prefixed hexadecimal +// strings with a Hex type. + +/** + * Represents a deposit. + */ +export interface Deposit { + /** + * Depositor's chain identifier. + */ + depositor: Identifier + + /** + * Deposit amount in satoshis. + */ + amount: BigNumber + + /** + * An 8-byte blinding factor as an un-prefixed hex string. Must be unique + * for the given depositor, wallet public key and refund public key. + */ + blindingFactor: string + + /** + * Public key hash of the wallet that is meant to receive the deposit. Must + * be an unprefixed hex string (without 0x prefix). + * + * You can use `computeHash160` function to get the hash from a plain text public key. + */ + walletPublicKeyHash: string + + /** + * Public key hash that is meant to be used during deposit refund after the + * locktime passes. Must be an unprefixed hex string (without 0x prefix). + * + * You can use `computeHash160` function to get the hash from a plain text public key. + */ + refundPublicKeyHash: string + + /** + * A 4-byte little-endian refund locktime as an un-prefixed hex string. + */ + refundLocktime: string + + /** + * Optional identifier of the vault the deposit should be routed in. + */ + vault?: Identifier +} + +/** + * Helper type that groups deposit's fields required to assemble a deposit + * script. + */ +export type DepositScriptParameters = Pick< + Deposit, + | "depositor" + | "blindingFactor" + | "refundLocktime" + | "walletPublicKeyHash" + | "refundPublicKeyHash" +> & {} + +/** + * Represents a deposit revealed to the on-chain bridge. This type emphasizes + * the on-chain state of the revealed deposit and omits the deposit script + * parameters as they are not relevant in this context. + */ +export type RevealedDeposit = Pick< + Deposit, + "depositor" | "amount" | "vault" +> & { + /** + * UNIX timestamp the deposit was revealed at. + */ + revealedAt: number + /** + * UNIX timestamp the request was swept at. If not swept yet, this parameter + * should have zero as value. + */ + sweptAt: number + /** + * Value of the treasury fee calculated for this revealed deposit. + * Denominated in satoshi. + */ + treasuryFee: BigNumber +} + +/** + * Represents an event emitted on deposit reveal to the on-chain bridge. + */ +export type DepositRevealedEvent = Deposit & { + fundingTxHash: TransactionHash + fundingOutputIndex: number +} & Event + /** * Represents a redemption request. */ diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 0dc461d44..824a8c286 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -14,12 +14,10 @@ import { WalletState, RedemptionRequest, RedemptionRequestedEvent, -} from "../contracts" -import { DepositRevealedEvent, DepositScriptParameters, RevealedDeposit, -} from "../../deposit" +} from "../contracts" import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber, constants, ContractTransaction, utils } from "ethers" import { backoffRetrier, Hex } from "../utils" diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index ebb835187..d5763354b 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -4,7 +4,8 @@ import { UnspentTransactionOutput, TransactionHash, } from "../../src/lib/bitcoin" -import { Deposit, calculateDepositRefundLocktime } from "../../src/deposit" +import { Deposit } from "../../src/lib/contracts" +import { calculateDepositRefundLocktime } from "../../src/deposit" import { Address } from "../../src/lib/ethereum" /** diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index e567dce0f..ba394fc4d 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -7,7 +7,8 @@ import { TransactionMerkleBranch, TransactionHash, } from "../../src/lib/bitcoin" -import { calculateDepositRefundLocktime, Deposit } from "../../src/deposit" +import { Deposit } from "../../src/lib/contracts" +import { calculateDepositRefundLocktime } from "../../src/deposit" import { BigNumber } from "ethers" import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src" diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index bab39a487..7e34edd9c 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -13,6 +13,11 @@ import { TransactionHash, UnspentTransactionOutput, } from "../src/lib/bitcoin" +import { + Deposit, + DepositScriptParameters, + RevealedDeposit, +} from "../src/lib/contracts" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import bcoin from "bcoin" import { @@ -21,11 +26,8 @@ import { calculateDepositAddress, calculateDepositRefundLocktime, calculateDepositScriptHash, - Deposit, - DepositScriptParameters, getRevealedDeposit, revealDeposit, - RevealedDeposit, submitDepositTransaction, suggestDepositWallet, } from "../src/deposit" diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index 30ee58aff..bdbca6683 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -7,6 +7,9 @@ import { Wallet, RedemptionRequest, RedemptionRequestedEvent, + Deposit, + DepositRevealedEvent, + RevealedDeposit, } from "../../src/lib/contracts" import { DecomposedRawTransaction, @@ -16,11 +19,6 @@ import { TransactionHash, } from "../../src/lib/bitcoin" import { BigNumberish, BigNumber, utils, constants } from "ethers" -import { - Deposit, - DepositRevealedEvent, - RevealedDeposit, -} from "../../src/deposit" import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src/lib/utils" From a4160bdb55cf2189a37c84ed1adcd49dc58e117b Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 25 Sep 2023 12:59:53 +0200 Subject: [PATCH 10/43] Move `proof.ts` to `lib/bitcoin` This content of the `proof.ts` file actually belongs to `lib/bitcoin/proof.ts`. Part of this code related to blocks, should live in `lib/bitcoin/block.ts`. --- typescript/src/deposit-sweep.ts | 2 +- typescript/src/index.ts | 4 +- typescript/src/lib/bitcoin/block.ts | 106 ++++++++ typescript/src/lib/bitcoin/proof.ts | 291 ++++++++++++++++++++ typescript/src/proof.ts | 399 ---------------------------- typescript/src/redemption.ts | 2 +- typescript/test/proof.test.ts | 12 +- 7 files changed, 406 insertions(+), 410 deletions(-) delete mode 100644 typescript/src/proof.ts diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 6b8b2344d..4a80b1cdc 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -1,6 +1,7 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { + assembleTransactionProof, RawTransaction, UnspentTransactionOutput, Client as BitcoinClient, @@ -12,7 +13,6 @@ import { } from "./lib/bitcoin" import { assembleDepositScript } from "./deposit" import { Bridge, Identifier, Deposit } from "./lib/contracts" -import { assembleTransactionProof } from "./proof" /** * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 66c92731e..8d2666446 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -1,5 +1,7 @@ // TODO: Consider exports refactoring as per discussion https://github.com/keep-network/tbtc-v2/pull/460#discussion_r1084530007 +import { validateTransactionProof } from "./lib/bitcoin" + import { calculateDepositAddress, getRevealedDeposit, @@ -23,8 +25,6 @@ import { getOptimisticMintingRequest, } from "./optimistic-minting" -import { validateTransactionProof } from "./proof" - export const TBTC = { calculateDepositAddress, suggestDepositWallet, diff --git a/typescript/src/lib/bitcoin/block.ts b/typescript/src/lib/bitcoin/block.ts index 6fc235d53..185ffa619 100644 --- a/typescript/src/lib/bitcoin/block.ts +++ b/typescript/src/lib/bitcoin/block.ts @@ -1,5 +1,6 @@ import { BigNumber } from "ethers" import { Hex } from "../utils" +import { computeHash256, hashLEToBigNumber } from "./hash" /** * BlockHeader represents the header of a Bitcoin block. For reference, see: @@ -141,3 +142,108 @@ export function targetToDifficulty(target: BigNumber): BigNumber { ) return DIFF1_TARGET.div(target) } + +/** + * Validates a chain of consecutive block headers by checking each header's + * difficulty, hash, and continuity with the previous header. This function can + * be used to validate a series of Bitcoin block headers for their validity. + * @param blockHeaders An array of block headers that form the chain to be + * validated. + * @param previousEpochDifficulty The difficulty of the previous Bitcoin epoch. + * @param currentEpochDifficulty The difficulty of the current Bitcoin epoch. + * @dev The block headers must come from Bitcoin epochs with difficulties marked + * by the previous and current difficulties. If a Bitcoin difficulty relay + * is used to provide these values and the relay is up-to-date, only the + * recent block headers will pass validation. Block headers older than the + * current and previous Bitcoin epochs will fail. + * @throws {Error} If any of the block headers are invalid, or if the block + * header chain is not continuous. + * @returns An empty return value. + */ +export function validateBlockHeadersChain( + blockHeaders: BlockHeader[], + previousEpochDifficulty: BigNumber, + currentEpochDifficulty: BigNumber +) { + let requireCurrentDifficulty: boolean = false + let previousBlockHeaderHash: Hex = Hex.from("00") + + for (let index = 0; index < blockHeaders.length; index++) { + const currentHeader = blockHeaders[index] + + // Check if the current block header stores the hash of the previously + // processed block header. Skip the check for the first header. + if (index !== 0) { + if ( + !previousBlockHeaderHash.equals(currentHeader.previousBlockHeaderHash) + ) { + throw new Error("Invalid headers chain") + } + } + + const difficultyTarget = bitsToTarget(currentHeader.bits) + + const currentBlockHeaderHash = computeHash256( + serializeBlockHeader(currentHeader) + ) + + // Ensure the header has sufficient work. + if (hashLEToBigNumber(currentBlockHeaderHash).gt(difficultyTarget)) { + throw new Error("Insufficient work in the header") + } + + // Save the current block header hash to compare it with the next block + // header's previous block header hash. + previousBlockHeaderHash = currentBlockHeaderHash + + // Check if the stored block difficulty is equal to previous or current + // difficulties. + const difficulty = targetToDifficulty(difficultyTarget) + + if (previousEpochDifficulty.eq(1) && currentEpochDifficulty.eq(1)) { + // Special case for Bitcoin Testnet. Do not check block's difficulty + // due to required difficulty falling to `1` for Testnet. + continue + } + + if ( + !difficulty.eq(previousEpochDifficulty) && + !difficulty.eq(currentEpochDifficulty) + ) { + throw new Error( + "Header difficulty not at current or previous Bitcoin difficulty" + ) + } + + // Additionally, require the header to be at current difficulty if some + // headers at current difficulty have already been seen. This ensures + // there is at most one switch from previous to current difficulties. + if (requireCurrentDifficulty && !difficulty.eq(currentEpochDifficulty)) { + throw new Error("Header must be at current Bitcoin difficulty") + } + + // If the header is at current difficulty, require the subsequent headers to + // be at current difficulty as well. + requireCurrentDifficulty = difficulty.eq(currentEpochDifficulty) + } +} + +/** + * Splits Bitcoin block headers in the raw format into an array of BlockHeaders. + * @param blockHeaders - string that contains block headers in the raw format. + * @returns Array of BlockHeader objects. + */ +export function splitBlockHeadersChain(blockHeaders: string): BlockHeader[] { + if (blockHeaders.length % 160 !== 0) { + throw new Error("Incorrect length of Bitcoin headers") + } + + const result: BlockHeader[] = [] + for (let i = 0; i < blockHeaders.length; i += 160) { + result.push( + deserializeBlockHeader(Hex.from(blockHeaders.substring(i, i + 160))) + ) + } + + return result +} diff --git a/typescript/src/lib/bitcoin/proof.ts b/typescript/src/lib/bitcoin/proof.ts index efbf4f8dd..60ce9128d 100644 --- a/typescript/src/lib/bitcoin/proof.ts +++ b/typescript/src/lib/bitcoin/proof.ts @@ -1,3 +1,14 @@ +import { Transaction, TransactionHash } from "./transaction" +import { Client } from "./client" +import { BigNumber } from "ethers" +import { + BlockHeader, + splitBlockHeadersChain, + validateBlockHeadersChain, +} from "./block" +import { Hex } from "../utils" +import { computeHash256 } from "./hash" + /** * Data required to perform a proof that a given transaction was included in * the Bitcoin blockchain. @@ -42,3 +53,283 @@ export interface TransactionMerkleBranch { */ position: number } + +/** + * Assembles a proof that a given transaction was included in the blockchain and + * has accumulated the required number of confirmations. + * @param transactionHash - Hash of the transaction being proven. + * @param requiredConfirmations - Required number of confirmations. + * @param bitcoinClient - Bitcoin client used to interact with the network. + * @returns Bitcoin transaction along with the inclusion proof. + */ +export async function assembleTransactionProof( + transactionHash: TransactionHash, + requiredConfirmations: number, + bitcoinClient: Client +): Promise { + const transaction = await bitcoinClient.getTransaction(transactionHash) + const confirmations = await bitcoinClient.getTransactionConfirmations( + transactionHash + ) + + if (confirmations < requiredConfirmations) { + throw new Error( + "Transaction confirmations number[" + + confirmations + + "] is not enough, required [" + + requiredConfirmations + + "]" + ) + } + + const latestBlockHeight = await bitcoinClient.latestBlockHeight() + const txBlockHeight = latestBlockHeight - confirmations + 1 + + // We subtract `1` from `requiredConfirmations` because the header at + // `txBlockHeight` is already included in the headers chain and is considered + // the first confirmation. So we only need to retrieve `requiredConfirmations - 1` + // subsequent block headers to reach the desired number of confirmations for + // the transaction. + const headersChain = await bitcoinClient.getHeadersChain( + txBlockHeight, + requiredConfirmations - 1 + ) + + const merkleBranch = await bitcoinClient.getTransactionMerkle( + transactionHash, + txBlockHeight + ) + + const merkleProof = createMerkleProof(merkleBranch) + + const proof = { + merkleProof: merkleProof, + txIndexInBlock: merkleBranch.position, + bitcoinHeaders: headersChain, + } + + return { ...transaction, ...proof } +} + +/** + * Create a proof of transaction inclusion in the block by concatenating + * 32-byte-long hash values. The values are converted to little endian form. + * @param txMerkleBranch - Branch of a Merkle tree leading to a transaction. + * @returns Transaction inclusion proof in hexadecimal form. + */ +function createMerkleProof(txMerkleBranch: TransactionMerkleBranch): string { + let proof = Buffer.from("") + txMerkleBranch.merkle.forEach(function (item) { + proof = Buffer.concat([proof, Buffer.from(item, "hex").reverse()]) + }) + return proof.toString("hex") +} + +/** + * Proves that a transaction with the given hash is included in the Bitcoin + * blockchain by validating the transaction's inclusion in the Merkle tree and + * verifying that the block containing the transaction has enough confirmations. + * @param transactionHash The hash of the transaction to be validated. + * @param requiredConfirmations The number of confirmations required for the + * transaction to be considered valid. The transaction has 1 confirmation + * when it is in the block at the current blockchain tip. Every subsequent + * block added to the blockchain is one additional confirmation. + * @param previousDifficulty The difficulty of the previous Bitcoin epoch. + * @param currentDifficulty The difficulty of the current Bitcoin epoch. + * @param bitcoinClient The client for interacting with the Bitcoin blockchain. + * @throws {Error} If the transaction is not included in the Bitcoin blockchain + * or if the block containing the transaction does not have enough + * confirmations. + * @dev The function should be used within a try-catch block. + * @returns An empty return value. + */ +export async function validateTransactionProof( + transactionHash: TransactionHash, + requiredConfirmations: number, + previousDifficulty: BigNumber, + currentDifficulty: BigNumber, + bitcoinClient: Client +) { + if (requiredConfirmations < 1) { + throw new Error("The number of required confirmations but at least 1") + } + + const proof = await assembleTransactionProof( + transactionHash, + requiredConfirmations, + bitcoinClient + ) + + const bitcoinHeaders: BlockHeader[] = splitBlockHeadersChain( + proof.bitcoinHeaders + ) + if (bitcoinHeaders.length != requiredConfirmations) { + throw new Error("Wrong number of confirmations") + } + + const merkleRootHash: Hex = bitcoinHeaders[0].merkleRootHash + const intermediateNodeHashes: Hex[] = splitMerkleProof(proof.merkleProof) + + validateMerkleTree( + transactionHash, + merkleRootHash, + intermediateNodeHashes, + proof.txIndexInBlock + ) + + validateBlockHeadersChain( + bitcoinHeaders, + previousDifficulty, + currentDifficulty + ) +} + +/** + * Validates the Merkle tree by checking if the provided transaction hash, + * Merkle root hash, intermediate node hashes, and transaction index parameters + * produce a valid Merkle proof. + * @param transactionHash The hash of the transaction being validated. + * @param merkleRootHash The Merkle root hash that the intermediate node hashes + * should compute to. + * @param intermediateNodeHashes The Merkle tree intermediate node hashes. + * This is a list of hashes the transaction being validated is paired + * with in the Merkle tree. + * @param transactionIndex The index of the transaction being validated within + * the block, used to determine the path to traverse in the Merkle tree. + * @throws {Error} If the Merkle tree is not valid. + * @returns An empty return value. + */ +function validateMerkleTree( + transactionHash: TransactionHash, + merkleRootHash: Hex, + intermediateNodeHashes: Hex[], + transactionIndex: number +) { + // Shortcut for a block that contains only a single transaction (coinbase). + if ( + transactionHash.reverse().equals(merkleRootHash) && + transactionIndex == 0 && + intermediateNodeHashes.length == 0 + ) { + return + } + + validateMerkleTreeHashes( + transactionHash, + merkleRootHash, + intermediateNodeHashes, + transactionIndex + ) +} + +/** + * Validates the transaction's Merkle proof by traversing the Merkle tree + * starting from the provided transaction hash and using the intermediate node + * hashes to compute the root hash. If the computed root hash does not match the + * merkle root hash, an error is thrown. + * @param transactionHash The hash of the transaction being validated. + * @param merkleRootHash The Merkle root hash that the intermediate nodes should + * compute to. + * @param intermediateNodeHashes The Merkle tree intermediate node hashes. + * This is a list of hashes the transaction being validated is paired + * with in the Merkle tree. + * @param transactionIndex The index of the transaction in the block, used + * to determine the path to traverse in the Merkle tree. + * @throws {Error} If the intermediate nodes are of an invalid length or if the + * computed root hash does not match the merkle root hash parameter. + * @returns An empty return value. + */ +function validateMerkleTreeHashes( + transactionHash: TransactionHash, + merkleRootHash: Hex, + intermediateNodeHashes: Hex[], + transactionIndex: number +) { + // To prove the transaction inclusion in a block we only need the hashes that + // form a path from the transaction being validated to the Merkle root hash. + // If the Merkle tree looks like this: + // + // h_01234567 + // / \ + // h_0123 h_4567 + // / \ / \ + // h_01 h_23 h_45 h_67 + // / \ / \ / \ / \ + // h_0 h_1 h_2 h_3 h_4 h_5 h_6 h_7 + // + // and the transaction hash to be validated is h_5 the following data + // will be used: + // - `transactionHash`: h_5 + // - `merkleRootHash`: h_01234567 + // - `intermediateNodeHashes`: [h_4, h_67, h_0123] + // - `transactionIndex`: 5 + // + // The following calculations will be performed: + // - h_4 and h_5 will be hashed to obtain h_45 + // - h_45 and h_67 will be hashed to obtain h_4567 + // - h_0123 will be hashed with h_4567 to obtain h_1234567 (Merkle root hash). + + // Note that when we move up the Merkle tree calculating parent hashes we need + // to join children hashes. The information which child hash should go first + // is obtained from `transactionIndex`. When `transactionIndex` is odd the + // hash taken from `intermediateNodeHashes` must go first. If it is even the + // hash from previous calculation must go first. The `transactionIndex` is + // divided by `2` after every hash calculation. + + if (intermediateNodeHashes.length === 0) { + throw new Error("Invalid merkle tree") + } + + let idx = transactionIndex + let currentHash = transactionHash.reverse() + + // Move up the Merkle tree hashing current hash value with hashes taken + // from `intermediateNodeHashes`. Use `idx` to determine the order of joining + // children hashes. + for (let i = 0; i < intermediateNodeHashes.length; i++) { + if (idx % 2 === 1) { + // If the current value of idx is odd the hash taken from + // `intermediateNodeHashes` goes before the current hash. + currentHash = computeHash256( + Hex.from(intermediateNodeHashes[i].toString() + currentHash.toString()) + ) + } else { + // If the current value of idx is even the hash taken from the current + // hash goes before the hash taken from `intermediateNodeHashes`. + currentHash = computeHash256( + Hex.from(currentHash.toString() + intermediateNodeHashes[i].toString()) + ) + } + + // Divide the value of `idx` by `2` when we move one level up the Merkle + // tree. + idx = idx >> 1 + } + + // Verify we arrived at the same value of Merkle root hash as the one stored + // in the block header. + if (!currentHash.equals(merkleRootHash)) { + throw new Error( + "Transaction Merkle proof is not valid for provided header and transaction hash" + ) + } +} + +/** + * Splits a given Merkle proof string into an array of intermediate node hashes. + * @param merkleProof A string representation of the Merkle proof. + * @returns An array of intermediate node hashes. + * @throws {Error} If the length of the Merkle proof is not a multiple of 64. + */ +function splitMerkleProof(merkleProof: string): Hex[] { + if (merkleProof.length % 64 != 0) { + throw new Error("Incorrect length of Merkle proof") + } + + const intermediateNodeHashes: Hex[] = [] + for (let i = 0; i < merkleProof.length; i += 64) { + intermediateNodeHashes.push(Hex.from(merkleProof.slice(i, i + 64))) + } + + return intermediateNodeHashes +} diff --git a/typescript/src/proof.ts b/typescript/src/proof.ts deleted file mode 100644 index 5cddfcba0..000000000 --- a/typescript/src/proof.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { - Transaction, - Proof, - TransactionMerkleBranch, - Client as BitcoinClient, - TransactionHash, - computeHash256, - deserializeBlockHeader, - bitsToTarget, - targetToDifficulty, - hashLEToBigNumber, - serializeBlockHeader, - BlockHeader, -} from "./lib/bitcoin" -import { BigNumber } from "ethers" -import { Hex } from "./lib/utils" - -/** - * Assembles a proof that a given transaction was included in the blockchain and - * has accumulated the required number of confirmations. - * @param transactionHash - Hash of the transaction being proven. - * @param requiredConfirmations - Required number of confirmations. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @returns Bitcoin transaction along with the inclusion proof. - */ -export async function assembleTransactionProof( - transactionHash: TransactionHash, - requiredConfirmations: number, - bitcoinClient: BitcoinClient -): Promise { - const transaction = await bitcoinClient.getTransaction(transactionHash) - const confirmations = await bitcoinClient.getTransactionConfirmations( - transactionHash - ) - - if (confirmations < requiredConfirmations) { - throw new Error( - "Transaction confirmations number[" + - confirmations + - "] is not enough, required [" + - requiredConfirmations + - "]" - ) - } - - const latestBlockHeight = await bitcoinClient.latestBlockHeight() - const txBlockHeight = latestBlockHeight - confirmations + 1 - - // We subtract `1` from `requiredConfirmations` because the header at - // `txBlockHeight` is already included in the headers chain and is considered - // the first confirmation. So we only need to retrieve `requiredConfirmations - 1` - // subsequent block headers to reach the desired number of confirmations for - // the transaction. - const headersChain = await bitcoinClient.getHeadersChain( - txBlockHeight, - requiredConfirmations - 1 - ) - - const merkleBranch = await bitcoinClient.getTransactionMerkle( - transactionHash, - txBlockHeight - ) - - const merkleProof = createMerkleProof(merkleBranch) - - const proof = { - merkleProof: merkleProof, - txIndexInBlock: merkleBranch.position, - bitcoinHeaders: headersChain, - } - - return { ...transaction, ...proof } -} - -/** - * Create a proof of transaction inclusion in the block by concatenating - * 32-byte-long hash values. The values are converted to little endian form. - * @param txMerkleBranch - Branch of a Merkle tree leading to a transaction. - * @returns Transaction inclusion proof in hexadecimal form. - */ -function createMerkleProof(txMerkleBranch: TransactionMerkleBranch): string { - let proof = Buffer.from("") - txMerkleBranch.merkle.forEach(function (item) { - proof = Buffer.concat([proof, Buffer.from(item, "hex").reverse()]) - }) - return proof.toString("hex") -} - -/** - * Proves that a transaction with the given hash is included in the Bitcoin - * blockchain by validating the transaction's inclusion in the Merkle tree and - * verifying that the block containing the transaction has enough confirmations. - * @param transactionHash The hash of the transaction to be validated. - * @param requiredConfirmations The number of confirmations required for the - * transaction to be considered valid. The transaction has 1 confirmation - * when it is in the block at the current blockchain tip. Every subsequent - * block added to the blockchain is one additional confirmation. - * @param previousDifficulty The difficulty of the previous Bitcoin epoch. - * @param currentDifficulty The difficulty of the current Bitcoin epoch. - * @param bitcoinClient The client for interacting with the Bitcoin blockchain. - * @throws {Error} If the transaction is not included in the Bitcoin blockchain - * or if the block containing the transaction does not have enough - * confirmations. - * @dev The function should be used within a try-catch block. - * @returns An empty return value. - */ -export async function validateTransactionProof( - transactionHash: TransactionHash, - requiredConfirmations: number, - previousDifficulty: BigNumber, - currentDifficulty: BigNumber, - bitcoinClient: BitcoinClient -) { - if (requiredConfirmations < 1) { - throw new Error("The number of required confirmations but at least 1") - } - - const proof = await assembleTransactionProof( - transactionHash, - requiredConfirmations, - bitcoinClient - ) - - const bitcoinHeaders: BlockHeader[] = splitHeaders(proof.bitcoinHeaders) - if (bitcoinHeaders.length != requiredConfirmations) { - throw new Error("Wrong number of confirmations") - } - - const merkleRootHash: Hex = bitcoinHeaders[0].merkleRootHash - const intermediateNodeHashes: Hex[] = splitMerkleProof(proof.merkleProof) - - validateMerkleTree( - transactionHash, - merkleRootHash, - intermediateNodeHashes, - proof.txIndexInBlock - ) - - validateBlockHeadersChain( - bitcoinHeaders, - previousDifficulty, - currentDifficulty - ) -} - -/** - * Validates the Merkle tree by checking if the provided transaction hash, - * Merkle root hash, intermediate node hashes, and transaction index parameters - * produce a valid Merkle proof. - * @param transactionHash The hash of the transaction being validated. - * @param merkleRootHash The Merkle root hash that the intermediate node hashes - * should compute to. - * @param intermediateNodeHashes The Merkle tree intermediate node hashes. - * This is a list of hashes the transaction being validated is paired - * with in the Merkle tree. - * @param transactionIndex The index of the transaction being validated within - * the block, used to determine the path to traverse in the Merkle tree. - * @throws {Error} If the Merkle tree is not valid. - * @returns An empty return value. - */ -function validateMerkleTree( - transactionHash: TransactionHash, - merkleRootHash: Hex, - intermediateNodeHashes: Hex[], - transactionIndex: number -) { - // Shortcut for a block that contains only a single transaction (coinbase). - if ( - transactionHash.reverse().equals(merkleRootHash) && - transactionIndex == 0 && - intermediateNodeHashes.length == 0 - ) { - return - } - - validateMerkleTreeHashes( - transactionHash, - merkleRootHash, - intermediateNodeHashes, - transactionIndex - ) -} - -/** - * Validates the transaction's Merkle proof by traversing the Merkle tree - * starting from the provided transaction hash and using the intermediate node - * hashes to compute the root hash. If the computed root hash does not match the - * merkle root hash, an error is thrown. - * @param transactionHash The hash of the transaction being validated. - * @param merkleRootHash The Merkle root hash that the intermediate nodes should - * compute to. - * @param intermediateNodeHashes The Merkle tree intermediate node hashes. - * This is a list of hashes the transaction being validated is paired - * with in the Merkle tree. - * @param transactionIndex The index of the transaction in the block, used - * to determine the path to traverse in the Merkle tree. - * @throws {Error} If the intermediate nodes are of an invalid length or if the - * computed root hash does not match the merkle root hash parameter. - * @returns An empty return value. - */ -function validateMerkleTreeHashes( - transactionHash: TransactionHash, - merkleRootHash: Hex, - intermediateNodeHashes: Hex[], - transactionIndex: number -) { - // To prove the transaction inclusion in a block we only need the hashes that - // form a path from the transaction being validated to the Merkle root hash. - // If the Merkle tree looks like this: - // - // h_01234567 - // / \ - // h_0123 h_4567 - // / \ / \ - // h_01 h_23 h_45 h_67 - // / \ / \ / \ / \ - // h_0 h_1 h_2 h_3 h_4 h_5 h_6 h_7 - // - // and the transaction hash to be validated is h_5 the following data - // will be used: - // - `transactionHash`: h_5 - // - `merkleRootHash`: h_01234567 - // - `intermediateNodeHashes`: [h_4, h_67, h_0123] - // - `transactionIndex`: 5 - // - // The following calculations will be performed: - // - h_4 and h_5 will be hashed to obtain h_45 - // - h_45 and h_67 will be hashed to obtain h_4567 - // - h_0123 will be hashed with h_4567 to obtain h_1234567 (Merkle root hash). - - // Note that when we move up the Merkle tree calculating parent hashes we need - // to join children hashes. The information which child hash should go first - // is obtained from `transactionIndex`. When `transactionIndex` is odd the - // hash taken from `intermediateNodeHashes` must go first. If it is even the - // hash from previous calculation must go first. The `transactionIndex` is - // divided by `2` after every hash calculation. - - if (intermediateNodeHashes.length === 0) { - throw new Error("Invalid merkle tree") - } - - let idx = transactionIndex - let currentHash = transactionHash.reverse() - - // Move up the Merkle tree hashing current hash value with hashes taken - // from `intermediateNodeHashes`. Use `idx` to determine the order of joining - // children hashes. - for (let i = 0; i < intermediateNodeHashes.length; i++) { - if (idx % 2 === 1) { - // If the current value of idx is odd the hash taken from - // `intermediateNodeHashes` goes before the current hash. - currentHash = computeHash256( - Hex.from(intermediateNodeHashes[i].toString() + currentHash.toString()) - ) - } else { - // If the current value of idx is even the hash taken from the current - // hash goes before the hash taken from `intermediateNodeHashes`. - currentHash = computeHash256( - Hex.from(currentHash.toString() + intermediateNodeHashes[i].toString()) - ) - } - - // Divide the value of `idx` by `2` when we move one level up the Merkle - // tree. - idx = idx >> 1 - } - - // Verify we arrived at the same value of Merkle root hash as the one stored - // in the block header. - if (!currentHash.equals(merkleRootHash)) { - throw new Error( - "Transaction Merkle proof is not valid for provided header and transaction hash" - ) - } -} - -/** - * Validates a chain of consecutive block headers by checking each header's - * difficulty, hash, and continuity with the previous header. This function can - * be used to validate a series of Bitcoin block headers for their validity. - * @param blockHeaders An array of block headers that form the chain to be - * validated. - * @param previousEpochDifficulty The difficulty of the previous Bitcoin epoch. - * @param currentEpochDifficulty The difficulty of the current Bitcoin epoch. - * @dev The block headers must come from Bitcoin epochs with difficulties marked - * by the previous and current difficulties. If a Bitcoin difficulty relay - * is used to provide these values and the relay is up-to-date, only the - * recent block headers will pass validation. Block headers older than the - * current and previous Bitcoin epochs will fail. - * @throws {Error} If any of the block headers are invalid, or if the block - * header chain is not continuous. - * @returns An empty return value. - */ -function validateBlockHeadersChain( - blockHeaders: BlockHeader[], - previousEpochDifficulty: BigNumber, - currentEpochDifficulty: BigNumber -) { - let requireCurrentDifficulty: boolean = false - let previousBlockHeaderHash: Hex = Hex.from("00") - - for (let index = 0; index < blockHeaders.length; index++) { - const currentHeader = blockHeaders[index] - - // Check if the current block header stores the hash of the previously - // processed block header. Skip the check for the first header. - if (index !== 0) { - if ( - !previousBlockHeaderHash.equals(currentHeader.previousBlockHeaderHash) - ) { - throw new Error("Invalid headers chain") - } - } - - const difficultyTarget = bitsToTarget(currentHeader.bits) - - const currentBlockHeaderHash = computeHash256( - serializeBlockHeader(currentHeader) - ) - - // Ensure the header has sufficient work. - if (hashLEToBigNumber(currentBlockHeaderHash).gt(difficultyTarget)) { - throw new Error("Insufficient work in the header") - } - - // Save the current block header hash to compare it with the next block - // header's previous block header hash. - previousBlockHeaderHash = currentBlockHeaderHash - - // Check if the stored block difficulty is equal to previous or current - // difficulties. - const difficulty = targetToDifficulty(difficultyTarget) - - if (previousEpochDifficulty.eq(1) && currentEpochDifficulty.eq(1)) { - // Special case for Bitcoin Testnet. Do not check block's difficulty - // due to required difficulty falling to `1` for Testnet. - continue - } - - if ( - !difficulty.eq(previousEpochDifficulty) && - !difficulty.eq(currentEpochDifficulty) - ) { - throw new Error( - "Header difficulty not at current or previous Bitcoin difficulty" - ) - } - - // Additionally, require the header to be at current difficulty if some - // headers at current difficulty have already been seen. This ensures - // there is at most one switch from previous to current difficulties. - if (requireCurrentDifficulty && !difficulty.eq(currentEpochDifficulty)) { - throw new Error("Header must be at current Bitcoin difficulty") - } - - // If the header is at current difficulty, require the subsequent headers to - // be at current difficulty as well. - requireCurrentDifficulty = difficulty.eq(currentEpochDifficulty) - } -} - -/** - * Splits a given Merkle proof string into an array of intermediate node hashes. - * @param merkleProof A string representation of the Merkle proof. - * @returns An array of intermediate node hashes. - * @throws {Error} If the length of the Merkle proof is not a multiple of 64. - */ -export function splitMerkleProof(merkleProof: string): Hex[] { - if (merkleProof.length % 64 != 0) { - throw new Error("Incorrect length of Merkle proof") - } - - const intermediateNodeHashes: Hex[] = [] - for (let i = 0; i < merkleProof.length; i += 64) { - intermediateNodeHashes.push(Hex.from(merkleProof.slice(i, i + 64))) - } - - return intermediateNodeHashes -} - -/** - * Splits Bitcoin block headers in the raw format into an array of BlockHeaders. - * @param blockHeaders - string that contains block headers in the raw format. - * @returns Array of BlockHeader objects. - */ -export function splitHeaders(blockHeaders: string): BlockHeader[] { - if (blockHeaders.length % 160 !== 0) { - throw new Error("Incorrect length of Bitcoin headers") - } - - const result: BlockHeader[] = [] - for (let i = 0; i < blockHeaders.length; i += 160) { - result.push( - deserializeBlockHeader(Hex.from(blockHeaders.substring(i, i + 160))) - ) - } - - return result -} diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index e1a3f0528..8db6dd296 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -1,6 +1,7 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { + assembleTransactionProof, BitcoinNetwork, createKeyRing, decomposeRawTransaction, @@ -15,7 +16,6 @@ import { TBTCToken, WalletState, } from "./lib/contracts" -import { assembleTransactionProof } from "./proof" import { determineWalletMainUtxo } from "./wallet" import { Hex } from "./lib/utils" diff --git a/typescript/test/proof.test.ts b/typescript/test/proof.test.ts index bfe846ac0..37c47ea8d 100644 --- a/typescript/test/proof.test.ts +++ b/typescript/test/proof.test.ts @@ -4,6 +4,9 @@ import { Transaction, BlockHeader, Proof, + assembleTransactionProof, + validateTransactionProof, + splitBlockHeadersChain, } from "../src/lib/bitcoin" import { Hex } from "../src/lib/utils" import { @@ -15,11 +18,6 @@ import { ProofTestData, TransactionProofData, } from "./data/proof" -import { - assembleTransactionProof, - validateTransactionProof, - splitHeaders, -} from "../src/proof" import { expect } from "chai" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" @@ -285,7 +283,7 @@ describe("Proof", () => { it("should throw", async () => { // Corrupt data by modifying previous block header hash of one of the // headers. - const headers: BlockHeader[] = splitHeaders( + const headers: BlockHeader[] = splitBlockHeadersChain( transactionConfirmationsInOneEpochData.bitcoinChainData.headersChain ) headers[headers.length - 1].previousBlockHeaderHash = Hex.from( @@ -313,7 +311,7 @@ describe("Proof", () => { it("should throw", async () => { // Corrupt data by modifying the nonce of one of the headers, so that // the resulting hash will be above the required difficulty target. - const headers: BlockHeader[] = splitHeaders( + const headers: BlockHeader[] = splitBlockHeadersChain( transactionConfirmationsInOneEpochData.bitcoinChainData.headersChain ) headers[headers.length - 1].nonce++ From 6c637d1eb5377bcfb4c09644aa39851ea5498978 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 26 Sep 2023 10:19:49 +0200 Subject: [PATCH 11/43] Final iteration on `lib/bitcoin` Here we make a final rework of the `lib/bitcoin` module. The main work is focused on introducing a consistent naming system that doesn't cause name clashes outside and group related functions into separate components. --- typescript/src/deposit-refund.ts | 38 +- typescript/src/deposit-sweep.ts | 71 ++- typescript/src/deposit.ts | 46 +- typescript/src/index.ts | 12 +- typescript/src/lib/bitcoin/address.ts | 26 +- typescript/src/lib/bitcoin/client.ts | 29 +- typescript/src/lib/bitcoin/csuint.ts | 9 +- .../src/lib/bitcoin/{key.ts => ecdsa-key.ts} | 25 +- typescript/src/lib/bitcoin/hash.ts | 15 +- .../src/lib/bitcoin/{block.ts => header.ts} | 199 +++--- typescript/src/lib/bitcoin/index.ts | 8 +- .../src/lib/bitcoin/{proof.ts => spv.ts} | 51 +- .../src/lib/bitcoin/{transaction.ts => tx.ts} | 55 +- typescript/src/lib/contracts/bridge.ts | 30 +- typescript/src/lib/contracts/tbtc-token.ts | 4 +- typescript/src/lib/contracts/tbtc-vault.ts | 12 +- typescript/src/lib/electrum/client.ts | 101 ++- typescript/src/lib/ethereum/bridge.ts | 61 +- typescript/src/lib/ethereum/tbtc-token.ts | 12 +- typescript/src/lib/ethereum/tbtc-vault.ts | 14 +- typescript/src/optimistic-minting.ts | 10 +- typescript/src/redemption.ts | 57 +- typescript/src/wallet.ts | 25 +- typescript/test/bitcoin-network.test.ts | 8 +- typescript/test/bitcoin.test.ts | 588 +++++++++--------- typescript/test/data/deposit-refund.ts | 24 +- typescript/test/data/deposit-sweep.ts | 99 +-- typescript/test/data/deposit.ts | 12 +- typescript/test/data/electrum.ts | 22 +- typescript/test/data/proof.ts | 62 +- typescript/test/data/redemption.ts | 78 +-- typescript/test/deposit-refund.test.ts | 14 +- typescript/test/deposit-sweep.test.ts | 70 +-- typescript/test/deposit.test.ts | 46 +- typescript/test/ethereum.test.ts | 18 +- typescript/test/proof.test.ts | 47 +- typescript/test/redemption.test.ts | 100 ++- typescript/test/utils/mock-bitcoin-client.ts | 88 ++- typescript/test/utils/mock-bridge.ts | 50 +- typescript/test/utils/mock-tbtc-token.ts | 6 +- typescript/test/wallet.test.ts | 41 +- 41 files changed, 1163 insertions(+), 1120 deletions(-) rename typescript/src/lib/bitcoin/{key.ts => ecdsa-key.ts} (77%) rename typescript/src/lib/bitcoin/{block.ts => header.ts} (75%) rename typescript/src/lib/bitcoin/{proof.ts => spv.ts} (91%) rename typescript/src/lib/bitcoin/{transaction.ts => tx.ts} (76%) diff --git a/typescript/src/deposit-refund.ts b/typescript/src/deposit-refund.ts index 16fa8448d..d2fe47da8 100644 --- a/typescript/src/deposit-refund.ts +++ b/typescript/src/deposit-refund.ts @@ -1,13 +1,13 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { - createKeyRing, - RawTransaction, - Client as BitcoinClient, - TransactionHash, - UnspentTransactionOutput, - computeHash160, - isCompressedPublicKey, + BitcoinPrivateKeyUtils, + BitcoinRawTx, + BitcoinClient, + BitcoinTxHash, + BitcoinUtxo, + BitcoinHashUtils, + BitcoinPublicKeyUtils, } from "./lib/bitcoin" import { Deposit } from "./lib/contracts" import { @@ -38,11 +38,11 @@ import { export async function submitDepositRefundTransaction( bitcoinClient: BitcoinClient, fee: BigNumber, - utxo: UnspentTransactionOutput, + utxo: BitcoinUtxo, deposit: Deposit, refunderAddress: string, refunderPrivateKey: string -): Promise<{ transactionHash: TransactionHash }> { +): Promise<{ transactionHash: BitcoinTxHash }> { const utxoRawTransaction = await bitcoinClient.getRawTransaction( utxo.transactionHash ) @@ -86,17 +86,18 @@ export async function submitDepositRefundTransaction( */ export async function assembleDepositRefundTransaction( fee: BigNumber, - utxo: UnspentTransactionOutput & RawTransaction, + utxo: BitcoinUtxo & BitcoinRawTx, deposit: Deposit, refunderAddress: string, refunderPrivateKey: string ): Promise<{ - transactionHash: TransactionHash - rawTransaction: RawTransaction + transactionHash: BitcoinTxHash + rawTransaction: BitcoinRawTx }> { validateInputParameters(deposit, utxo) - const refunderKeyRing = createKeyRing(refunderPrivateKey) + const refunderKeyRing = + BitcoinPrivateKeyUtils.createKeyRing(refunderPrivateKey) const transaction = new bcoin.MTX() @@ -149,7 +150,7 @@ export async function assembleDepositRefundTransaction( throw new Error("Transaction verification failure") } - const transactionHash = TransactionHash.from(transaction.txid()) + const transactionHash = BitcoinTxHash.from(transaction.txid()) return { transactionHash, @@ -186,7 +187,7 @@ async function prepareInputSignData( const refunderPublicKey = refunderKeyRing.getPublicKey("hex") if ( - computeHash160(refunderKeyRing.getPublicKey("hex")) != + BitcoinHashUtils.computeHash160(refunderKeyRing.getPublicKey("hex")) != deposit.refundPublicKeyHash ) { throw new Error( @@ -194,7 +195,7 @@ async function prepareInputSignData( ) } - if (!isCompressedPublicKey(refunderPublicKey)) { + if (!BitcoinPublicKeyUtils.isCompressedPublicKey(refunderPublicKey)) { throw new Error("Refunder public key must be compressed") } @@ -315,10 +316,7 @@ function locktimeToUnixTimestamp(locktime: string): number { * @param utxo - UTXO that was created during depositing that needs be refunded. * @returns Empty return. */ -function validateInputParameters( - deposit: Deposit, - utxo: UnspentTransactionOutput -) { +function validateInputParameters(deposit: Deposit, utxo: BitcoinUtxo) { validateDepositScriptParameters(deposit) if (!deposit.amount.eq(utxo.value)) { diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 4a80b1cdc..6b20c142f 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -1,15 +1,15 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { - assembleTransactionProof, - RawTransaction, - UnspentTransactionOutput, - Client as BitcoinClient, - decomposeRawTransaction, - isCompressedPublicKey, - createKeyRing, - TransactionHash, - computeHash160, + assembleBitcoinSpvProof, + BitcoinRawTx, + BitcoinUtxo, + BitcoinClient, + extractBitcoinRawTxVectors, + BitcoinPublicKeyUtils, + BitcoinPrivateKeyUtils, + BitcoinTxHash, + BitcoinHashUtils, } from "./lib/bitcoin" import { assembleDepositScript } from "./deposit" import { Bridge, Identifier, Deposit } from "./lib/contracts" @@ -41,14 +41,14 @@ export async function submitDepositSweepTransaction( fee: BigNumber, walletPrivateKey: string, witness: boolean, - utxos: UnspentTransactionOutput[], + utxos: BitcoinUtxo[], deposits: Deposit[], - mainUtxo?: UnspentTransactionOutput + mainUtxo?: BitcoinUtxo ): Promise<{ - transactionHash: TransactionHash - newMainUtxo: UnspentTransactionOutput + transactionHash: BitcoinTxHash + newMainUtxo: BitcoinUtxo }> { - const utxosWithRaw: (UnspentTransactionOutput & RawTransaction)[] = [] + const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] for (const utxo of utxos) { const utxoRawTransaction = await bitcoinClient.getRawTransaction( utxo.transactionHash @@ -114,13 +114,13 @@ export async function assembleDepositSweepTransaction( fee: BigNumber, walletPrivateKey: string, witness: boolean, - utxos: (UnspentTransactionOutput & RawTransaction)[], + utxos: (BitcoinUtxo & BitcoinRawTx)[], deposits: Deposit[], - mainUtxo?: UnspentTransactionOutput & RawTransaction + mainUtxo?: BitcoinUtxo & BitcoinRawTx ): Promise<{ - transactionHash: TransactionHash - newMainUtxo: UnspentTransactionOutput - rawTransaction: RawTransaction + transactionHash: BitcoinTxHash + newMainUtxo: BitcoinUtxo + rawTransaction: BitcoinRawTx }> { if (utxos.length < 1) { throw new Error("There must be at least one deposit UTXO to sweep") @@ -130,7 +130,10 @@ export async function assembleDepositSweepTransaction( throw new Error("Number of UTXOs must equal the number of deposit elements") } - const walletKeyRing = createKeyRing(walletPrivateKey, witness) + const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( + walletPrivateKey, + witness + ) const walletAddress = walletKeyRing.getAddress("string") const inputCoins = [] @@ -177,12 +180,12 @@ export async function assembleDepositSweepTransaction( // UTXOs must be mapped to deposits, as `fund` may arrange inputs in any // order - const utxosWithDeposits: (UnspentTransactionOutput & - RawTransaction & - Deposit)[] = utxos.map((utxo, index) => ({ - ...utxo, - ...deposits[index], - })) + const utxosWithDeposits: (BitcoinUtxo & BitcoinRawTx & Deposit)[] = utxos.map( + (utxo, index) => ({ + ...utxo, + ...deposits[index], + }) + ) for (let i = 0; i < transaction.inputs.length; i++) { const previousOutpoint = transaction.inputs[i].prevout @@ -220,7 +223,7 @@ export async function assembleDepositSweepTransaction( } } - const transactionHash = TransactionHash.from(transaction.txid()) + const transactionHash = BitcoinTxHash.from(transaction.txid()) return { transactionHash, @@ -359,7 +362,7 @@ async function prepareInputSignData( const walletPublicKey = walletKeyRing.getPublicKey("hex") if ( - computeHash160(walletKeyRing.getPublicKey("hex")) != + BitcoinHashUtils.computeHash160(walletKeyRing.getPublicKey("hex")) != deposit.walletPublicKeyHash ) { throw new Error( @@ -367,7 +370,7 @@ async function prepareInputSignData( ) } - if (!isCompressedPublicKey(walletPublicKey)) { + if (!BitcoinPublicKeyUtils.isCompressedPublicKey(walletPublicKey)) { throw new Error("Wallet public key must be compressed") } @@ -396,14 +399,14 @@ async function prepareInputSignData( * @returns Empty promise. */ export async function submitDepositSweepProof( - transactionHash: TransactionHash, - mainUtxo: UnspentTransactionOutput, + transactionHash: BitcoinTxHash, + mainUtxo: BitcoinUtxo, bridge: Bridge, bitcoinClient: BitcoinClient, vault?: Identifier ): Promise { const confirmations = await bridge.txProofDifficultyFactor() - const proof = await assembleTransactionProof( + const proof = await assembleBitcoinSpvProof( transactionHash, confirmations, bitcoinClient @@ -412,9 +415,9 @@ export async function submitDepositSweepProof( // proof to the decomposed transaction data (version, inputs, outputs, locktime). // Use raw transaction data for now. const rawTransaction = await bitcoinClient.getRawTransaction(transactionHash) - const decomposedRawTransaction = decomposeRawTransaction(rawTransaction) + const rawTransactionVectors = extractBitcoinRawTxVectors(rawTransaction) await bridge.submitDepositSweepProof( - decomposedRawTransaction, + rawTransactionVectors, proof, mainUtxo, vault diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index 7d66bc7e8..78b2b8fcc 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -1,15 +1,14 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { - Client as BitcoinClient, + BitcoinClient, BitcoinNetwork, toBcoinNetwork, - decomposeRawTransaction, - createKeyRing, - RawTransaction, - UnspentTransactionOutput, - TransactionHash, - isPublicKeyHashLength, + extractBitcoinRawTxVectors, + BitcoinPrivateKeyUtils, + BitcoinRawTx, + BitcoinUtxo, + BitcoinTxHash, } from "./lib/bitcoin" import { Bridge, @@ -40,17 +39,18 @@ export async function submitDepositTransaction( bitcoinClient: BitcoinClient, witness: boolean ): Promise<{ - transactionHash: TransactionHash - depositUtxo: UnspentTransactionOutput + transactionHash: BitcoinTxHash + depositUtxo: BitcoinUtxo }> { - const depositorKeyRing = createKeyRing(depositorPrivateKey) + const depositorKeyRing = + BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) const depositorAddress = depositorKeyRing.getAddress("string") const utxos = await bitcoinClient.findAllUnspentTransactionOutputs( depositorAddress ) - const utxosWithRaw: (UnspentTransactionOutput & RawTransaction)[] = [] + const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] for (const utxo of utxos) { const utxoRawTransaction = await bitcoinClient.getRawTransaction( utxo.transactionHash @@ -92,15 +92,16 @@ export async function submitDepositTransaction( */ export async function assembleDepositTransaction( deposit: Deposit, - utxos: (UnspentTransactionOutput & RawTransaction)[], + utxos: (BitcoinUtxo & BitcoinRawTx)[], depositorPrivateKey: string, witness: boolean ): Promise<{ - transactionHash: TransactionHash - depositUtxo: UnspentTransactionOutput - rawTransaction: RawTransaction + transactionHash: BitcoinTxHash + depositUtxo: BitcoinUtxo + rawTransaction: BitcoinRawTx }> { - const depositorKeyRing = createKeyRing(depositorPrivateKey) + const depositorKeyRing = + BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) const depositorAddress = depositorKeyRing.getAddress("string") const inputCoins = utxos.map((utxo) => @@ -130,7 +131,7 @@ export async function assembleDepositTransaction( transaction.sign(depositorKeyRing) - const transactionHash = TransactionHash.from(transaction.txid()) + const transactionHash = BitcoinTxHash.from(transaction.txid()) return { transactionHash, @@ -198,12 +199,11 @@ export function validateDepositScriptParameters( if (deposit.blindingFactor.length != 16) { throw new Error("Blinding factor must be an 8-byte number") } - - if (!isPublicKeyHashLength(deposit.walletPublicKeyHash)) { + if (deposit.walletPublicKeyHash.length != 40) { throw new Error("Invalid wallet public key hash") } - if (!isPublicKeyHashLength(deposit.refundPublicKeyHash)) { + if (deposit.refundPublicKeyHash.length != 40) { throw new Error("Invalid refund public key hash") } @@ -294,13 +294,13 @@ export async function calculateDepositAddress( * that matches the given deposit data. */ export async function revealDeposit( - utxo: UnspentTransactionOutput, + utxo: BitcoinUtxo, deposit: DepositScriptParameters, bitcoinClient: BitcoinClient, bridge: Bridge, vault?: Identifier ): Promise { - const depositTx = decomposeRawTransaction( + const depositTx = extractBitcoinRawTxVectors( await bitcoinClient.getRawTransaction(utxo.transactionHash) ) @@ -314,7 +314,7 @@ export async function revealDeposit( * @returns Revealed deposit data. */ export async function getRevealedDeposit( - utxo: UnspentTransactionOutput, + utxo: BitcoinUtxo, bridge: Bridge ): Promise { return bridge.deposits(utxo.transactionHash, utxo.outputIndex) diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 8d2666446..c522694f8 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -1,6 +1,6 @@ // TODO: Consider exports refactoring as per discussion https://github.com/keep-network/tbtc-v2/pull/460#discussion_r1084530007 -import { validateTransactionProof } from "./lib/bitcoin" +import { validateBitcoinSpvProof } from "./lib/bitcoin" import { calculateDepositAddress, @@ -48,14 +48,14 @@ export const OptimisticMinting = { } export const Bitcoin = { - validateTransactionProof, + validateBitcoinSpvProof, } export { - TransactionHash as BitcoinTransactionHash, - Transaction as BitcoinTransaction, - TransactionOutput as BitcoinTransactionOutput, - locktimeToNumber as BitcoinLocktimeToNumber, + BitcoinTxHash, + BitcoinTx, + BitcoinTxOutput, + BitcoinLocktimeUtils, BitcoinNetwork, } from "./lib/bitcoin" diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts index 354cb3db7..874d995ab 100644 --- a/typescript/src/lib/bitcoin/address.ts +++ b/typescript/src/lib/bitcoin/address.ts @@ -3,7 +3,7 @@ import { Hex } from "../utils" import { BitcoinNetwork, toBcoinNetwork } from "./network" /** - * Encodes a public key hash into a P2PKH/P2WPKH address. + * Converts a public key hash into a P2PKH/P2WPKH address. * @param publicKeyHash - public key hash that will be encoded. Must be an * unprefixed hex string (without 0x prefix). * @param witness - If true, a witness public key hash will be encoded and @@ -12,7 +12,7 @@ import { BitcoinNetwork, toBcoinNetwork } from "./network" * @returns P2PKH or P2WPKH address encoded from the given public key hash * @throws Throws an error if network is not supported. */ -export function encodeToBitcoinAddress( +function publicKeyHashToAddress( publicKeyHash: string, witness: boolean, network: BitcoinNetwork @@ -25,13 +25,13 @@ export function encodeToBitcoinAddress( } /** - * Decodes P2PKH or P2WPKH address into a public key hash. Throws if the + * Converts a P2PKH or P2WPKH address into a public key hash. Throws if the * provided address is not PKH-based. * @param address - P2PKH or P2WPKH address that will be decoded. * @returns Public key hash decoded from the address. This will be an unprefixed * hex string (without 0x prefix). */ -export function decodeBitcoinAddress(address: string): string { +function addressToPublicKeyHash(address: string): string { const addressObject = new bcoin.Address(address) const isPKH = @@ -44,21 +44,21 @@ export function decodeBitcoinAddress(address: string): string { } /** - * Creates the output script from the BTC address. + * Converts an address to the respective output script. * @param address BTC address. * @returns The un-prefixed and not prepended with length output script. */ -export function createOutputScriptFromAddress(address: string): Hex { +function addressToOutputScript(address: string): Hex { return Hex.from(Script.fromAddress(address).toRaw().toString("hex")) } /** - * Creates the Bitcoin address from the output script. + * Converts an output script to the respective network-specific address. * @param script The unprefixed and not prepended with length output script. * @param network Bitcoin network. * @returns The Bitcoin address. */ -export function createAddressFromOutputScript( +function outputScriptToAddress( script: Hex, network: BitcoinNetwork = BitcoinNetwork.Mainnet ): string { @@ -66,3 +66,13 @@ export function createAddressFromOutputScript( .getAddress() ?.toString(toBcoinNetwork(network)) } + +/** + * Utility functions allowing to perform Bitcoin address conversions. + */ +export const BitcoinAddressConverter = { + publicKeyHashToAddress, + addressToPublicKeyHash, + addressToOutputScript, + outputScriptToAddress, +} diff --git a/typescript/src/lib/bitcoin/client.ts b/typescript/src/lib/bitcoin/client.ts index 0d1e10e99..6e3d512fe 100644 --- a/typescript/src/lib/bitcoin/client.ts +++ b/typescript/src/lib/bitcoin/client.ts @@ -1,16 +1,11 @@ import { BitcoinNetwork } from "./network" -import { - RawTransaction, - Transaction, - TransactionHash, - UnspentTransactionOutput, -} from "./transaction" -import { TransactionMerkleBranch } from "./proof" +import { BitcoinRawTx, BitcoinTx, BitcoinTxHash, BitcoinUtxo } from "./tx" +import { BitcoinTxMerkleBranch } from "./spv" /** * Represents a Bitcoin client. */ -export interface Client { +export interface BitcoinClient { /** * Gets the network supported by the server the client connected to. * @returns Bitcoin network. @@ -22,9 +17,7 @@ export interface Client { * @param address - Bitcoin address UTXOs should be determined for. * @returns List of UTXOs. */ - findAllUnspentTransactionOutputs( - address: string - ): Promise + findAllUnspentTransactionOutputs(address: string): Promise /** * Gets the history of confirmed transactions for given Bitcoin address. @@ -36,21 +29,21 @@ export interface Client { * a specific number of last transaction. For example, limit = 5 will * return only the last 5 transactions for the given address. */ - getTransactionHistory(address: string, limit?: number): Promise + getTransactionHistory(address: string, limit?: number): Promise /** * Gets the full transaction object for given transaction hash. * @param transactionHash - Hash of the transaction. * @returns Transaction object. */ - getTransaction(transactionHash: TransactionHash): Promise + getTransaction(transactionHash: BitcoinTxHash): Promise /** * Gets the raw transaction data for given transaction hash. * @param transactionHash - Hash of the transaction. * @returns Raw transaction. */ - getRawTransaction(transactionHash: TransactionHash): Promise + getRawTransaction(transactionHash: BitcoinTxHash): Promise /** * Gets the number of confirmations that a given transaction has accumulated @@ -58,7 +51,7 @@ export interface Client { * @param transactionHash - Hash of the transaction. * @returns The number of confirmations. */ - getTransactionConfirmations(transactionHash: TransactionHash): Promise + getTransactionConfirmations(transactionHash: BitcoinTxHash): Promise /** * Gets height of the latest mined block. @@ -82,13 +75,13 @@ export interface Client { * @return Merkle branch. */ getTransactionMerkle( - transactionHash: TransactionHash, + transactionHash: BitcoinTxHash, blockHeight: number - ): Promise + ): Promise /** * Broadcasts the given transaction over the network. * @param transaction - Transaction to broadcast. */ - broadcast(transaction: RawTransaction): Promise + broadcast(transaction: BitcoinRawTx): Promise } diff --git a/typescript/src/lib/bitcoin/csuint.ts b/typescript/src/lib/bitcoin/csuint.ts index 613736103..e0576e176 100644 --- a/typescript/src/lib/bitcoin/csuint.ts +++ b/typescript/src/lib/bitcoin/csuint.ts @@ -10,7 +10,7 @@ import { Hex } from "../utils" * @returns An object holding the value of the compact size uint along with the * compact size uint byte length. */ -export function readCompactSizeUint(varLenData: Hex): { +function read(varLenData: Hex): { value: number byteLength: number } { @@ -39,3 +39,10 @@ export function readCompactSizeUint(varLenData: Hex): { } } } + +/** + * Utility functions allowing to deal with Bitcoin compact size uints. + */ +export const BitcoinCompactSizeUint = { + read, +} diff --git a/typescript/src/lib/bitcoin/key.ts b/typescript/src/lib/bitcoin/ecdsa-key.ts similarity index 77% rename from typescript/src/lib/bitcoin/key.ts rename to typescript/src/lib/bitcoin/ecdsa-key.ts index 968bd2bed..16a6b1ec7 100644 --- a/typescript/src/lib/bitcoin/key.ts +++ b/typescript/src/lib/bitcoin/ecdsa-key.ts @@ -8,7 +8,7 @@ import { Hex } from "../utils" * @param publicKey - Public key that should be checked. * @returns True if the key is a compressed Bitcoin public key, false otherwise. */ -export function isCompressedPublicKey(publicKey: string): boolean { +function isCompressedPublicKey(publicKey: string): boolean { // Must have 33 bytes and 02 or 03 prefix. return ( publicKey.length == 66 && @@ -21,7 +21,7 @@ export function isCompressedPublicKey(publicKey: string): boolean { * @param publicKey Uncompressed 64-byte public key as an unprefixed hex string. * @returns Compressed 33-byte public key prefixed with 02 or 03. */ -export function compressPublicKey(publicKey: string | Hex): string { +function compressPublicKey(publicKey: string | Hex): string { if (typeof publicKey === "string") { publicKey = Hex.from(publicKey) } @@ -51,12 +51,11 @@ export function compressPublicKey(publicKey: string | Hex): string { } /** - * Checks if given public key hash has proper length (20-byte) - * @param publicKeyHash - text that will be checked for the correct length - * @returns true if the given string is 20-byte long, false otherwise + * Utility functions allowing to perform Bitcoin ECDSA public keys. */ -export function isPublicKeyHashLength(publicKeyHash: string): boolean { - return publicKeyHash.length === 40 +export const BitcoinPublicKeyUtils = { + isCompressedPublicKey, + compressPublicKey, } /** @@ -66,10 +65,7 @@ export function isPublicKeyHashLength(publicKeyHash: string): boolean { * or non-witness addresses * @returns Bitcoin key ring. */ -export function createKeyRing( - privateKey: string, - witness: boolean = true -): any { +function createKeyRing(privateKey: string, witness: boolean = true): any { const decodedPrivateKey = wif.decode(privateKey) return new bcoin.KeyRing({ @@ -78,3 +74,10 @@ export function createKeyRing( compressed: decodedPrivateKey.compressed, }) } + +/** + * Utility functions allowing to perform Bitcoin ECDSA public keys. + */ +export const BitcoinPrivateKeyUtils = { + createKeyRing, +} diff --git a/typescript/src/lib/bitcoin/hash.ts b/typescript/src/lib/bitcoin/hash.ts index bef166140..31426edf9 100644 --- a/typescript/src/lib/bitcoin/hash.ts +++ b/typescript/src/lib/bitcoin/hash.ts @@ -6,7 +6,7 @@ import { Hex } from "../utils" * @param text - Text the HASH160 is computed for. * @returns Hash as a 20-byte un-prefixed hex string. */ -export function computeHash160(text: string): string { +function computeHash160(text: string): string { const sha256Hash = utils.sha256( Hex.from(Buffer.from(text, "hex")).toPrefixedString() ) @@ -20,7 +20,7 @@ export function computeHash160(text: string): string { * @param text - Text the double SHA256 is computed for. * @returns Hash as a 32-byte un-prefixed hex string. */ -export function computeHash256(text: Hex): Hex { +function computeHash256(text: Hex): Hex { const firstHash = utils.sha256(text.toPrefixedString()) const secondHash = utils.sha256(firstHash) @@ -32,6 +32,15 @@ export function computeHash256(text: Hex): Hex { * @param hash - Hash in hex-string format. * @returns BigNumber representation of the hash. */ -export function hashLEToBigNumber(hash: Hex): BigNumber { +function hashLEToBigNumber(hash: Hex): BigNumber { return BigNumber.from(hash.reverse().toPrefixedString()) } + +/** + * Utility functions allowing to deal with Bitcoin hashes. + */ +export const BitcoinHashUtils = { + computeHash160, + computeHash256, + hashLEToBigNumber, +} diff --git a/typescript/src/lib/bitcoin/block.ts b/typescript/src/lib/bitcoin/header.ts similarity index 75% rename from typescript/src/lib/bitcoin/block.ts rename to typescript/src/lib/bitcoin/header.ts index 185ffa619..06531d933 100644 --- a/typescript/src/lib/bitcoin/block.ts +++ b/typescript/src/lib/bitcoin/header.ts @@ -1,12 +1,12 @@ import { BigNumber } from "ethers" import { Hex } from "../utils" -import { computeHash256, hashLEToBigNumber } from "./hash" +import { BitcoinHashUtils } from "./hash" /** - * BlockHeader represents the header of a Bitcoin block. For reference, see: + * BitcoinHeader represents the header of a Bitcoin block. For reference, see: * https://developer.bitcoin.org/reference/block_chain.html#block-headers. */ -export interface BlockHeader { +export interface BitcoinHeader { /** * The block version number that indicates which set of block validation rules * to follow. The field is 4-byte long. @@ -45,28 +45,28 @@ export interface BlockHeader { } /** - * Serializes a BlockHeader to the raw representation. - * @param blockHeader - block header. - * @returns Serialized block header. + * Serializes a Bitcoin block header to the raw representation. + * @param header - Bitcoin block header. + * @returns Serialized Bitcoin block header. */ -export function serializeBlockHeader(blockHeader: BlockHeader): Hex { +function serializeHeader(header: BitcoinHeader): Hex { const buffer = Buffer.alloc(80) - buffer.writeUInt32LE(blockHeader.version, 0) - blockHeader.previousBlockHeaderHash.toBuffer().copy(buffer, 4) - blockHeader.merkleRootHash.toBuffer().copy(buffer, 36) - buffer.writeUInt32LE(blockHeader.time, 68) - buffer.writeUInt32LE(blockHeader.bits, 72) - buffer.writeUInt32LE(blockHeader.nonce, 76) + buffer.writeUInt32LE(header.version, 0) + header.previousBlockHeaderHash.toBuffer().copy(buffer, 4) + header.merkleRootHash.toBuffer().copy(buffer, 36) + buffer.writeUInt32LE(header.time, 68) + buffer.writeUInt32LE(header.bits, 72) + buffer.writeUInt32LE(header.nonce, 76) return Hex.from(buffer) } /** - * Deserializes a block header in the raw representation to BlockHeader. - * @param rawBlockHeader - BlockHeader in the raw format. - * @returns Block header as a BlockHeader. + * Deserializes a raw representation of a Bitcoin block header. + * @param rawHeader - Raw Bitcoin block header. + * @returns Deserialized Bitcoin block header. */ -export function deserializeBlockHeader(rawBlockHeader: Hex): BlockHeader { - const buffer = rawBlockHeader.toBuffer() +function deserializeHeader(rawHeader: Hex): BitcoinHeader { + const buffer = rawHeader.toBuffer() const version = buffer.readUInt32LE(0) const previousBlockHeaderHash = buffer.slice(4, 36) const merkleRootHash = buffer.slice(36, 68) @@ -85,69 +85,39 @@ export function deserializeBlockHeader(rawBlockHeader: Hex): BlockHeader { } /** - * Converts a block header's bits into target. - * @param bits - bits from block header. - * @returns Target as a BigNumber. + * Deserializes a raw representation of a Bitcoin block headers chain. + * @param rawHeadersChain - Raw Bitcoin block headers chain. + * @returns Deserialized Bitcoin block headers. */ -export function bitsToTarget(bits: number): BigNumber { - // A serialized 80-byte block header stores the `bits` value as a 4-byte - // little-endian hexadecimal value in a slot including bytes 73, 74, 75, and - // 76. This function's input argument is expected to be a numerical - // representation of that 4-byte value reverted to the big-endian order. - // For example, if the `bits` little-endian value in the header is - // `0xcb04041b`, it must be reverted to the big-endian form `0x1b0404cb` and - // turned to a decimal number `453248203` in order to be used as this - // function's input. - // - // The `bits` 4-byte big-endian representation is a compact value that works - // like a base-256 version of scientific notation. It encodes the target - // exponent in the first byte and the target mantissa in the last three bytes. - // Referring to the previous example, if `bits = 453248203`, the hexadecimal - // representation is `0x1b0404cb` so the exponent is `0x1b` while the mantissa - // is `0x0404cb`. - // - // To extract the exponent, we need to shift right by 3 bytes (24 bits), - // extract the last byte of the result, and subtract 3 (because of the - // mantissa length): - // - 0x1b0404cb >>> 24 = 0x0000001b - // - 0x0000001b & 0xff = 0x1b - // - 0x1b - 3 = 24 (decimal) - // - // To extract the mantissa, we just need to take the last three bytes: - // - 0x1b0404cb & 0xffffff = 0x0404cb = 263371 (decimal) - // - // The final difficulty can be computed as mantissa * 256^exponent: - // - 263371 * 256^24 = - // 1653206561150525499452195696179626311675293455763937233695932416 (decimal) - // - // Sources: - // - https://developer.bitcoin.org/reference/block_chain.html#target-nbits - // - https://wiki.bitcoinsv.io/index.php/Target +function deserializeHeadersChain(rawHeadersChain: string): BitcoinHeader[] { + if (rawHeadersChain.length % 160 !== 0) { + throw new Error("Incorrect length of Bitcoin headers") + } - const exponent = ((bits >>> 24) & 0xff) - 3 - const mantissa = bits & 0xffffff + const result: BitcoinHeader[] = [] + for (let i = 0; i < rawHeadersChain.length; i += 160) { + result.push( + deserializeHeader(Hex.from(rawHeadersChain.substring(i, i + 160))) + ) + } - const target = BigNumber.from(mantissa).mul(BigNumber.from(256).pow(exponent)) - return target + return result } /** - * Converts difficulty target to difficulty. - * @param target - difficulty target. - * @returns Difficulty as a BigNumber. + * Utility functions allowing to serialize and deserialize Bitcoin block headers. */ -export function targetToDifficulty(target: BigNumber): BigNumber { - const DIFF1_TARGET = BigNumber.from( - "0xffff0000000000000000000000000000000000000000000000000000" - ) - return DIFF1_TARGET.div(target) +export const BitcoinHeaderSerializer = { + serializeHeader, + deserializeHeader, + deserializeHeadersChain, } /** * Validates a chain of consecutive block headers by checking each header's * difficulty, hash, and continuity with the previous header. This function can * be used to validate a series of Bitcoin block headers for their validity. - * @param blockHeaders An array of block headers that form the chain to be + * @param headers An array of block headers that form the chain to be * validated. * @param previousEpochDifficulty The difficulty of the previous Bitcoin epoch. * @param currentEpochDifficulty The difficulty of the current Bitcoin epoch. @@ -160,16 +130,16 @@ export function targetToDifficulty(target: BigNumber): BigNumber { * header chain is not continuous. * @returns An empty return value. */ -export function validateBlockHeadersChain( - blockHeaders: BlockHeader[], +export function validateBitcoinHeadersChain( + headers: BitcoinHeader[], previousEpochDifficulty: BigNumber, currentEpochDifficulty: BigNumber ) { let requireCurrentDifficulty: boolean = false let previousBlockHeaderHash: Hex = Hex.from("00") - for (let index = 0; index < blockHeaders.length; index++) { - const currentHeader = blockHeaders[index] + for (let index = 0; index < headers.length; index++) { + const currentHeader = headers[index] // Check if the current block header stores the hash of the previously // processed block header. Skip the check for the first header. @@ -183,12 +153,16 @@ export function validateBlockHeadersChain( const difficultyTarget = bitsToTarget(currentHeader.bits) - const currentBlockHeaderHash = computeHash256( - serializeBlockHeader(currentHeader) + const currentBlockHeaderHash = BitcoinHashUtils.computeHash256( + serializeHeader(currentHeader) ) // Ensure the header has sufficient work. - if (hashLEToBigNumber(currentBlockHeaderHash).gt(difficultyTarget)) { + if ( + BitcoinHashUtils.hashLEToBigNumber(currentBlockHeaderHash).gt( + difficultyTarget + ) + ) { throw new Error("Insufficient work in the header") } @@ -229,21 +203,68 @@ export function validateBlockHeadersChain( } /** - * Splits Bitcoin block headers in the raw format into an array of BlockHeaders. - * @param blockHeaders - string that contains block headers in the raw format. - * @returns Array of BlockHeader objects. + * Converts a block header's bits into target. + * @param bits - bits from block header. + * @returns Target as a BigNumber. */ -export function splitBlockHeadersChain(blockHeaders: string): BlockHeader[] { - if (blockHeaders.length % 160 !== 0) { - throw new Error("Incorrect length of Bitcoin headers") - } +function bitsToTarget(bits: number): BigNumber { + // A serialized 80-byte block header stores the `bits` value as a 4-byte + // little-endian hexadecimal value in a slot including bytes 73, 74, 75, and + // 76. This function's input argument is expected to be a numerical + // representation of that 4-byte value reverted to the big-endian order. + // For example, if the `bits` little-endian value in the header is + // `0xcb04041b`, it must be reverted to the big-endian form `0x1b0404cb` and + // turned to a decimal number `453248203` in order to be used as this + // function's input. + // + // The `bits` 4-byte big-endian representation is a compact value that works + // like a base-256 version of scientific notation. It encodes the target + // exponent in the first byte and the target mantissa in the last three bytes. + // Referring to the previous example, if `bits = 453248203`, the hexadecimal + // representation is `0x1b0404cb` so the exponent is `0x1b` while the mantissa + // is `0x0404cb`. + // + // To extract the exponent, we need to shift right by 3 bytes (24 bits), + // extract the last byte of the result, and subtract 3 (because of the + // mantissa length): + // - 0x1b0404cb >>> 24 = 0x0000001b + // - 0x0000001b & 0xff = 0x1b + // - 0x1b - 3 = 24 (decimal) + // + // To extract the mantissa, we just need to take the last three bytes: + // - 0x1b0404cb & 0xffffff = 0x0404cb = 263371 (decimal) + // + // The final difficulty can be computed as mantissa * 256^exponent: + // - 263371 * 256^24 = + // 1653206561150525499452195696179626311675293455763937233695932416 (decimal) + // + // Sources: + // - https://developer.bitcoin.org/reference/block_chain.html#target-nbits + // - https://wiki.bitcoinsv.io/index.php/Target - const result: BlockHeader[] = [] - for (let i = 0; i < blockHeaders.length; i += 160) { - result.push( - deserializeBlockHeader(Hex.from(blockHeaders.substring(i, i + 160))) - ) - } + const exponent = ((bits >>> 24) & 0xff) - 3 + const mantissa = bits & 0xffffff - return result + const target = BigNumber.from(mantissa).mul(BigNumber.from(256).pow(exponent)) + return target +} + +/** + * Converts difficulty target to difficulty. + * @param target - difficulty target. + * @returns Difficulty as a BigNumber. + */ +function targetToDifficulty(target: BigNumber): BigNumber { + const DIFF1_TARGET = BigNumber.from( + "0xffff0000000000000000000000000000000000000000000000000000" + ) + return DIFF1_TARGET.div(target) +} + +/** + * Utility functions allowing to perform Bitcoin target conversions. + */ +export const BitcoinTargetConverter = { + bitsToTarget, + targetToDifficulty, } diff --git a/typescript/src/lib/bitcoin/index.ts b/typescript/src/lib/bitcoin/index.ts index 2cc43b708..52028bc46 100644 --- a/typescript/src/lib/bitcoin/index.ts +++ b/typescript/src/lib/bitcoin/index.ts @@ -1,9 +1,9 @@ export * from "./address" -export * from "./block" export * from "./client" export * from "./csuint" +export * from "./ecdsa-key" export * from "./hash" -export * from "./key" +export * from "./header" export * from "./network" -export * from "./proof" -export * from "./transaction" +export * from "./spv" +export * from "./tx" diff --git a/typescript/src/lib/bitcoin/proof.ts b/typescript/src/lib/bitcoin/spv.ts similarity index 91% rename from typescript/src/lib/bitcoin/proof.ts rename to typescript/src/lib/bitcoin/spv.ts index 60ce9128d..4deb1cec4 100644 --- a/typescript/src/lib/bitcoin/proof.ts +++ b/typescript/src/lib/bitcoin/spv.ts @@ -1,19 +1,19 @@ -import { Transaction, TransactionHash } from "./transaction" -import { Client } from "./client" +import { BitcoinTx, BitcoinTxHash } from "./tx" +import { BitcoinClient } from "./client" import { BigNumber } from "ethers" import { - BlockHeader, - splitBlockHeadersChain, - validateBlockHeadersChain, -} from "./block" + BitcoinHeader, + BitcoinHeaderSerializer, + validateBitcoinHeadersChain, +} from "./header" import { Hex } from "../utils" -import { computeHash256 } from "./hash" +import { BitcoinHashUtils } from "./hash" /** * Data required to perform a proof that a given transaction was included in * the Bitcoin blockchain. */ -export interface Proof { +export interface BitcoinSpvProof { /** * The merkle proof of transaction inclusion in a block, as an un-prefixed * hex string. @@ -35,7 +35,7 @@ export interface Proof { /** * Information about the merkle branch to a confirmed transaction. */ -export interface TransactionMerkleBranch { +export interface BitcoinTxMerkleBranch { /** * The height of the block the transaction was confirmed in. */ @@ -62,11 +62,11 @@ export interface TransactionMerkleBranch { * @param bitcoinClient - Bitcoin client used to interact with the network. * @returns Bitcoin transaction along with the inclusion proof. */ -export async function assembleTransactionProof( - transactionHash: TransactionHash, +export async function assembleBitcoinSpvProof( + transactionHash: BitcoinTxHash, requiredConfirmations: number, - bitcoinClient: Client -): Promise { + bitcoinClient: BitcoinClient +): Promise { const transaction = await bitcoinClient.getTransaction(transactionHash) const confirmations = await bitcoinClient.getTransactionConfirmations( transactionHash @@ -117,7 +117,7 @@ export async function assembleTransactionProof( * @param txMerkleBranch - Branch of a Merkle tree leading to a transaction. * @returns Transaction inclusion proof in hexadecimal form. */ -function createMerkleProof(txMerkleBranch: TransactionMerkleBranch): string { +function createMerkleProof(txMerkleBranch: BitcoinTxMerkleBranch): string { let proof = Buffer.from("") txMerkleBranch.merkle.forEach(function (item) { proof = Buffer.concat([proof, Buffer.from(item, "hex").reverse()]) @@ -143,26 +143,25 @@ function createMerkleProof(txMerkleBranch: TransactionMerkleBranch): string { * @dev The function should be used within a try-catch block. * @returns An empty return value. */ -export async function validateTransactionProof( - transactionHash: TransactionHash, +export async function validateBitcoinSpvProof( + transactionHash: BitcoinTxHash, requiredConfirmations: number, previousDifficulty: BigNumber, currentDifficulty: BigNumber, - bitcoinClient: Client + bitcoinClient: BitcoinClient ) { if (requiredConfirmations < 1) { throw new Error("The number of required confirmations but at least 1") } - const proof = await assembleTransactionProof( + const proof = await assembleBitcoinSpvProof( transactionHash, requiredConfirmations, bitcoinClient ) - const bitcoinHeaders: BlockHeader[] = splitBlockHeadersChain( - proof.bitcoinHeaders - ) + const bitcoinHeaders: BitcoinHeader[] = + BitcoinHeaderSerializer.deserializeHeadersChain(proof.bitcoinHeaders) if (bitcoinHeaders.length != requiredConfirmations) { throw new Error("Wrong number of confirmations") } @@ -177,7 +176,7 @@ export async function validateTransactionProof( proof.txIndexInBlock ) - validateBlockHeadersChain( + validateBitcoinHeadersChain( bitcoinHeaders, previousDifficulty, currentDifficulty @@ -200,7 +199,7 @@ export async function validateTransactionProof( * @returns An empty return value. */ function validateMerkleTree( - transactionHash: TransactionHash, + transactionHash: BitcoinTxHash, merkleRootHash: Hex, intermediateNodeHashes: Hex[], transactionIndex: number @@ -240,7 +239,7 @@ function validateMerkleTree( * @returns An empty return value. */ function validateMerkleTreeHashes( - transactionHash: TransactionHash, + transactionHash: BitcoinTxHash, merkleRootHash: Hex, intermediateNodeHashes: Hex[], transactionIndex: number @@ -290,13 +289,13 @@ function validateMerkleTreeHashes( if (idx % 2 === 1) { // If the current value of idx is odd the hash taken from // `intermediateNodeHashes` goes before the current hash. - currentHash = computeHash256( + currentHash = BitcoinHashUtils.computeHash256( Hex.from(intermediateNodeHashes[i].toString() + currentHash.toString()) ) } else { // If the current value of idx is even the hash taken from the current // hash goes before the hash taken from `intermediateNodeHashes`. - currentHash = computeHash256( + currentHash = BitcoinHashUtils.computeHash256( Hex.from(currentHash.toString() + intermediateNodeHashes[i].toString()) ) } diff --git a/typescript/src/lib/bitcoin/transaction.ts b/typescript/src/lib/bitcoin/tx.ts similarity index 76% rename from typescript/src/lib/bitcoin/transaction.ts rename to typescript/src/lib/bitcoin/tx.ts index 7d9b69670..e51532dc1 100644 --- a/typescript/src/lib/bitcoin/transaction.ts +++ b/typescript/src/lib/bitcoin/tx.ts @@ -4,18 +4,18 @@ import { BigNumber } from "ethers" import { Hex } from "../utils" /** - * Represents a transaction hash (or transaction ID) as an un-prefixed hex + * Represents a Bitcoin transaction hash (or transaction ID) as an un-prefixed hex * string. This hash is supposed to have the same byte order as used by the * Bitcoin block explorers which is the opposite of the byte order used * by the Bitcoin protocol internally. That means the hash must be reversed in * the use cases that expect the Bitcoin internal byte order. */ -export class TransactionHash extends Hex {} +export class BitcoinTxHash extends Hex {} /** - * Represents a raw transaction. + * Represents a raw Bitcoin transaction. */ -export interface RawTransaction { +export interface BitcoinRawTx { /** * The full transaction payload as an un-prefixed hex string. */ @@ -23,33 +23,33 @@ export interface RawTransaction { } /** - * Data about a transaction. + * Data about a Bitcoin transaction. */ -export interface Transaction { +export interface BitcoinTx { /** * The transaction hash (or transaction ID) as an un-prefixed hex string. */ - transactionHash: TransactionHash + transactionHash: BitcoinTxHash /** * The vector of transaction inputs. */ - inputs: TransactionInput[] + inputs: BitcoinTxInput[] /** * The vector of transaction outputs. */ - outputs: TransactionOutput[] + outputs: BitcoinTxOutput[] } /** - * Data about a transaction outpoint. + * Data about a Bitcoin transaction outpoint. */ -export interface TransactionOutpoint { +export interface BitcoinTxOutpoint { /** * The hash of the transaction the outpoint belongs to. */ - transactionHash: TransactionHash + transactionHash: BitcoinTxHash /** * The zero-based index of the output from the specified transaction. @@ -58,9 +58,9 @@ export interface TransactionOutpoint { } /** - * Data about a transaction input. + * Data about a Bitcoin transaction input. */ -export type TransactionInput = TransactionOutpoint & { +export type BitcoinTxInput = BitcoinTxOutpoint & { /** * The scriptSig that unlocks the specified outpoint for spending. */ @@ -68,9 +68,9 @@ export type TransactionInput = TransactionOutpoint & { } /** - * Data about a transaction output. + * Data about a Bitcoin transaction output. */ -export interface TransactionOutput { +export interface BitcoinTxOutput { /** * The 0-based index of the output. */ @@ -88,9 +88,9 @@ export interface TransactionOutput { } /** - * Data about an unspent transaction output. + * Data about a Bitcoin unspent transaction output. */ -export type UnspentTransactionOutput = TransactionOutpoint & { +export type BitcoinUtxo = BitcoinTxOutpoint & { /** * The unspent value in satoshis. */ @@ -98,9 +98,9 @@ export type UnspentTransactionOutput = TransactionOutpoint & { } /** - * Represents data of decomposed raw transaction. + * Represents a raw Bitcoin transaction decomposed into specific vectors. */ -export interface DecomposedRawTransaction { +export interface BitcoinRawTxVectors { /** * Transaction version as an un-prefixed hex string. */ @@ -130,9 +130,9 @@ export interface DecomposedRawTransaction { * @param rawTransaction - Transaction in the raw format. * @returns Transaction data with fields represented as un-prefixed hex strings. */ -export function decomposeRawTransaction( - rawTransaction: RawTransaction -): DecomposedRawTransaction { +export function extractBitcoinRawTxVectors( + rawTransaction: BitcoinRawTx +): BitcoinRawTxVectors { const toHex = (bufferWriter: any) => { return bufferWriter.render().toString("hex") } @@ -189,7 +189,14 @@ export function decomposeRawTransaction( * hex string {@link: Deposit#refundLocktime}. * @returns UNIX timestamp in seconds. */ -export function locktimeToNumber(locktimeLE: Buffer | string): number { +function locktimeToNumber(locktimeLE: Buffer | string): number { const locktimeBE: Buffer = Hex.from(locktimeLE).reverse().toBuffer() return BigNumber.from(locktimeBE).toNumber() } + +/** + * Utility functions allowing to deal with Bitcoin locktime. + */ +export const BitcoinLocktimeUtils = { + locktimeToNumber, +} diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index c663dc527..b92d54911 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -1,9 +1,9 @@ import { BigNumber } from "ethers" import { - Proof, - UnspentTransactionOutput, - DecomposedRawTransaction, - TransactionHash, + BitcoinSpvProof, + BitcoinUtxo, + BitcoinRawTxVectors, + BitcoinTxHash, } from "../bitcoin" import { Hex } from "../utils" import { Event, GetEvents } from "./chain-event" @@ -29,9 +29,9 @@ export interface Bridge { * be routed in. */ submitDepositSweepProof( - sweepTx: DecomposedRawTransaction, - sweepProof: Proof, - mainUtxo: UnspentTransactionOutput, + sweepTx: BitcoinRawTxVectors, + sweepProof: BitcoinSpvProof, + mainUtxo: BitcoinUtxo, vault?: Identifier ): Promise @@ -46,7 +46,7 @@ export interface Bridge { * @returns Transaction hash of the reveal deposit transaction as string */ revealDeposit( - depositTx: DecomposedRawTransaction, + depositTx: BitcoinRawTxVectors, depositOutputIndex: number, deposit: DepositScriptParameters, vault?: Identifier @@ -60,7 +60,7 @@ export interface Bridge { * @returns Revealed deposit data. */ deposits( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise @@ -77,7 +77,7 @@ export interface Bridge { */ requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber ): Promise @@ -91,9 +91,9 @@ export interface Bridge { * compressed form (33 bytes long with 02 or 03 prefix). */ submitRedemptionProof( - redemptionTx: DecomposedRawTransaction, - redemptionProof: Proof, - mainUtxo: UnspentTransactionOutput, + redemptionTx: BitcoinRawTxVectors, + redemptionProof: BitcoinSpvProof, + mainUtxo: BitcoinUtxo, walletPublicKey: string ): Promise @@ -167,7 +167,7 @@ export interface Bridge { * @param utxo UTXO components. * @returns The hash of the UTXO. */ - buildUtxoHash(utxo: UnspentTransactionOutput): Hex + buildUtxoHash(utxo: BitcoinUtxo): Hex /** * Get emitted RedemptionRequested events. @@ -268,7 +268,7 @@ export type RevealedDeposit = Pick< * Represents an event emitted on deposit reveal to the on-chain bridge. */ export type DepositRevealedEvent = Deposit & { - fundingTxHash: TransactionHash + fundingTxHash: BitcoinTxHash fundingOutputIndex: number } & Event diff --git a/typescript/src/lib/contracts/tbtc-token.ts b/typescript/src/lib/contracts/tbtc-token.ts index dcfdd484a..9e32a4801 100644 --- a/typescript/src/lib/contracts/tbtc-token.ts +++ b/typescript/src/lib/contracts/tbtc-token.ts @@ -1,5 +1,5 @@ import { BigNumber } from "ethers" -import { UnspentTransactionOutput } from "../bitcoin" +import { BitcoinUtxo } from "../bitcoin" import { Hex } from "../utils" /** @@ -36,7 +36,7 @@ export interface TBTCToken { */ requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber ): Promise diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts index 732251000..8a61c5f8e 100644 --- a/typescript/src/lib/contracts/tbtc-vault.ts +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -1,4 +1,4 @@ -import { TransactionHash } from "../bitcoin" +import { BitcoinTxHash } from "../bitcoin" import { Hex } from "../utils" import { Identifier } from "./chain-identifier" import { Event, GetEvents } from "./chain-event" @@ -47,7 +47,7 @@ export interface TBTCVault { * @returns Transaction hash of the optimistic mint request transaction. */ requestOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise @@ -60,7 +60,7 @@ export interface TBTCVault { * @returns Transaction hash of the optimistic mint cancel transaction. */ cancelOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise @@ -73,7 +73,7 @@ export interface TBTCVault { * @returns Transaction hash of the optimistic mint finalize transaction. */ finalizeOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise @@ -85,7 +85,7 @@ export interface TBTCVault { * @returns Optimistic minting request. */ optimisticMintingRequests( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise @@ -153,7 +153,7 @@ export type OptimisticMintingRequestedEvent = { /** * Hash of a Bitcoin transaction made to fund the deposit. */ - fundingTxHash: TransactionHash + fundingTxHash: BitcoinTxHash /** * Index of an output in the funding transaction made to fund the deposit. */ diff --git a/typescript/src/lib/electrum/client.ts b/typescript/src/lib/electrum/client.ts index 0142e8037..cf12baca5 100644 --- a/typescript/src/lib/electrum/client.ts +++ b/typescript/src/lib/electrum/client.ts @@ -1,16 +1,16 @@ import bcoin from "bcoin" import pTimeout from "p-timeout" import { - Client as BitcoinClient, + BitcoinClient, BitcoinNetwork, - createOutputScriptFromAddress, - RawTransaction, - Transaction, - TransactionHash, - TransactionInput, - TransactionMerkleBranch, - TransactionOutput, - UnspentTransactionOutput, + BitcoinAddressConverter, + BitcoinRawTx, + BitcoinTx, + BitcoinTxHash, + BitcoinTxInput, + BitcoinTxMerkleBranch, + BitcoinTxOutput, + BitcoinUtxo, } from "../bitcoin" import Electrum from "electrum-client-js" import { BigNumber, utils } from "ethers" @@ -226,42 +226,37 @@ export class Client implements BitcoinClient { /** * @see {BitcoinClient#findAllUnspentTransactionOutputs} */ - findAllUnspentTransactionOutputs( - address: string - ): Promise { - return this.withElectrum( - async (electrum: Electrum) => { - const script = createOutputScriptFromAddress(address).toString() + findAllUnspentTransactionOutputs(address: string): Promise { + return this.withElectrum(async (electrum: Electrum) => { + const script = + BitcoinAddressConverter.addressToOutputScript(address).toString() - // eslint-disable-next-line camelcase - type UnspentOutput = { tx_pos: number; value: number; tx_hash: string } - - const unspentTransactions: UnspentOutput[] = - await this.withBackoffRetrier()(async () => { - return await electrum.blockchain_scripthash_listunspent( - computeScriptHash(script) - ) - }) - - return unspentTransactions.reverse().map((tx: UnspentOutput) => ({ - transactionHash: TransactionHash.from(tx.tx_hash), - outputIndex: tx.tx_pos, - value: BigNumber.from(tx.value), - })) - } - ) + // eslint-disable-next-line camelcase + type UnspentOutput = { tx_pos: number; value: number; tx_hash: string } + + const unspentTransactions: UnspentOutput[] = + await this.withBackoffRetrier()(async () => { + return await electrum.blockchain_scripthash_listunspent( + computeScriptHash(script) + ) + }) + + return unspentTransactions.reverse().map((tx: UnspentOutput) => ({ + transactionHash: BitcoinTxHash.from(tx.tx_hash), + outputIndex: tx.tx_pos, + value: BigNumber.from(tx.value), + })) + }) } // eslint-disable-next-line valid-jsdoc /** * @see {BitcoinClient#getTransactionHistory} */ - getTransactionHistory( - address: string, - limit?: number - ): Promise { - return this.withElectrum(async (electrum: Electrum) => { - const script = createOutputScriptFromAddress(address).toString() + getTransactionHistory(address: string, limit?: number): Promise { + return this.withElectrum(async (electrum: Electrum) => { + const script = + BitcoinAddressConverter.addressToOutputScript(address).toString() // eslint-disable-next-line camelcase type HistoryItem = { height: number; tx_hash: string } @@ -297,7 +292,7 @@ export class Client implements BitcoinClient { } const transactions = historyItems.map((item) => - this.getTransaction(TransactionHash.from(item.tx_hash)) + this.getTransaction(BitcoinTxHash.from(item.tx_hash)) ) return Promise.all(transactions) @@ -308,8 +303,8 @@ export class Client implements BitcoinClient { /** * @see {BitcoinClient#getTransaction} */ - getTransaction(transactionHash: TransactionHash): Promise { - return this.withElectrum(async (electrum: Electrum) => { + getTransaction(transactionHash: BitcoinTxHash): Promise { + return this.withElectrum(async (electrum: Electrum) => { // We cannot use `blockchain_transaction_get` with `verbose = true` argument // to get the the transaction details as Esplora/Electrs doesn't support verbose // transactions. @@ -331,15 +326,15 @@ export class Client implements BitcoinClient { const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") const inputs = transaction.inputs.map( - (input: any): TransactionInput => ({ - transactionHash: TransactionHash.from(input.prevout.hash).reverse(), + (input: any): BitcoinTxInput => ({ + transactionHash: BitcoinTxHash.from(input.prevout.hash).reverse(), outputIndex: input.prevout.index, scriptSig: Hex.from(input.script.toRaw()), }) ) const outputs = transaction.outputs.map( - (output: any, i: number): TransactionOutput => ({ + (output: any, i: number): BitcoinTxOutput => ({ outputIndex: i, value: BigNumber.from(output.value), scriptPubKey: Hex.from(output.script.toRaw()), @@ -347,7 +342,7 @@ export class Client implements BitcoinClient { ) return { - transactionHash: TransactionHash.from(transaction.hash()).reverse(), + transactionHash: BitcoinTxHash.from(transaction.hash()).reverse(), inputs: inputs, outputs: outputs, } @@ -358,8 +353,8 @@ export class Client implements BitcoinClient { /** * @see {BitcoinClient#getRawTransaction} */ - getRawTransaction(transactionHash: TransactionHash): Promise { - return this.withElectrum(async (electrum: Electrum) => { + getRawTransaction(transactionHash: BitcoinTxHash): Promise { + return this.withElectrum(async (electrum: Electrum) => { const transaction: string = await this.withBackoffRetrier()( async () => { return await electrum.blockchain_transaction_get( @@ -379,9 +374,7 @@ export class Client implements BitcoinClient { /** * @see {BitcoinClient#getTransactionConfirmations} */ - getTransactionConfirmations( - transactionHash: TransactionHash - ): Promise { + getTransactionConfirmations(transactionHash: BitcoinTxHash): Promise { // We cannot use `blockchain_transaction_get` with `verbose = true` argument // to get the the transaction details as Esplora/Electrs doesn't support verbose // transactions. @@ -507,10 +500,10 @@ export class Client implements BitcoinClient { * @see {BitcoinClient#getTransactionMerkle} */ getTransactionMerkle( - transactionHash: TransactionHash, + transactionHash: BitcoinTxHash, blockHeight: number - ): Promise { - return this.withElectrum( + ): Promise { + return this.withElectrum( async (electrum: Electrum) => { const merkle = await this.withBackoffRetrier<{ // eslint-disable-next-line camelcase @@ -537,7 +530,7 @@ export class Client implements BitcoinClient { /** * @see {BitcoinClient#broadcast} */ - broadcast(transaction: RawTransaction): Promise { + broadcast(transaction: BitcoinRawTx): Promise { return this.withElectrum(async (electrum: Electrum) => { await this.withBackoffRetrier()(async () => { return await electrum.blockchain_transaction_broadcast( diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 824a8c286..03da23321 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -22,13 +22,13 @@ import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber, constants, ContractTransaction, utils } from "ethers" import { backoffRetrier, Hex } from "../utils" import { - compressPublicKey, - computeHash160, - DecomposedRawTransaction, - Proof, - readCompactSizeUint, - TransactionHash, - UnspentTransactionOutput, + BitcoinPublicKeyUtils, + BitcoinHashUtils, + BitcoinRawTxVectors, + BitcoinSpvProof, + BitcoinCompactSizeUint, + BitcoinTxHash, + BitcoinUtxo, } from "../bitcoin" import { ContractConfig, @@ -75,9 +75,7 @@ export class Bridge blockNumber: BigNumber.from(event.blockNumber).toNumber(), blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), - fundingTxHash: TransactionHash.from( - event.args!.fundingTxHash - ).reverse(), + fundingTxHash: BitcoinTxHash.from(event.args!.fundingTxHash).reverse(), fundingOutputIndex: BigNumber.from( event.args!.fundingOutputIndex ).toNumber(), @@ -104,7 +102,7 @@ export class Bridge redeemerOutputScript: string ): Promise { const redemptionKey = Bridge.buildRedemptionKey( - computeHash160(walletPublicKey), + BitcoinHashUtils.computeHash160(walletPublicKey), redeemerOutputScript ) @@ -127,7 +125,7 @@ export class Bridge redeemerOutputScript: string ): Promise { const redemptionKey = Bridge.buildRedemptionKey( - computeHash160(walletPublicKey), + BitcoinHashUtils.computeHash160(walletPublicKey), redeemerOutputScript ) @@ -200,7 +198,7 @@ export class Bridge * @see {ChainBridge#revealDeposit} */ async revealDeposit( - depositTx: DecomposedRawTransaction, + depositTx: BitcoinRawTxVectors, depositOutputIndex: number, deposit: DepositScriptParameters, vault?: ChainIdentifier @@ -238,9 +236,9 @@ export class Bridge * @see {ChainBridge#submitDepositSweepProof} */ async submitDepositSweepProof( - sweepTx: DecomposedRawTransaction, - sweepProof: Proof, - mainUtxo: UnspentTransactionOutput, + sweepTx: BitcoinRawTxVectors, + sweepProof: BitcoinSpvProof, + mainUtxo: BitcoinUtxo, vault?: ChainIdentifier ): Promise { const sweepTxParam = { @@ -298,11 +296,13 @@ export class Bridge */ async requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber ): Promise { - const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` + const walletPublicKeyHash = `0x${BitcoinHashUtils.computeHash160( + walletPublicKey + )}` const mainUtxoParam = { // The Ethereum Bridge expects this hash to be in the Bitcoin internal @@ -335,9 +335,9 @@ export class Bridge * @see {ChainBridge#submitRedemptionProof} */ async submitRedemptionProof( - redemptionTx: DecomposedRawTransaction, - redemptionProof: Proof, - mainUtxo: UnspentTransactionOutput, + redemptionTx: BitcoinRawTxVectors, + redemptionProof: BitcoinSpvProof, + mainUtxo: BitcoinUtxo, walletPublicKey: string ): Promise { const redemptionTxParam = { @@ -361,7 +361,9 @@ export class Bridge txOutputValue: mainUtxo.value, } - const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` + const walletPublicKeyHash = `0x${BitcoinHashUtils.computeHash160( + walletPublicKey + )}` await sendWithRetry(async () => { return await this._instance.submitRedemptionProof( @@ -378,7 +380,7 @@ export class Bridge * @see {ChainBridge#deposits} */ async deposits( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) @@ -401,7 +403,7 @@ export class Bridge * @returns Deposit key. */ static buildDepositKey( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): string { const prefixedReversedDepositTxHash = depositTxHash @@ -466,7 +468,9 @@ export class Bridge ecdsaWalletID ) - return Hex.from(compressPublicKey(uncompressedPublicKey)) + return Hex.from( + BitcoinPublicKeyUtils.compressPublicKey(uncompressedPublicKey) + ) } // eslint-disable-next-line valid-jsdoc @@ -561,7 +565,7 @@ export class Bridge * * @see {ChainBridge#buildUtxoHash} */ - buildUtxoHash(utxo: UnspentTransactionOutput): Hex { + buildUtxoHash(utxo: BitcoinUtxo): Hex { return Hex.from( utils.solidityKeccak256( ["bytes32", "uint32", "uint64"], @@ -600,7 +604,10 @@ export class Bridge ) const redeemerOutputScript = prefixedRedeemerOutputScript .toString() - .slice(readCompactSizeUint(prefixedRedeemerOutputScript).byteLength * 2) + .slice( + BitcoinCompactSizeUint.read(prefixedRedeemerOutputScript).byteLength * + 2 + ) return { blockNumber: BigNumber.from(event.blockNumber).toNumber(), diff --git a/typescript/src/lib/ethereum/tbtc-token.ts b/typescript/src/lib/ethereum/tbtc-token.ts index 51f1aa480..825a8c920 100644 --- a/typescript/src/lib/ethereum/tbtc-token.ts +++ b/typescript/src/lib/ethereum/tbtc-token.ts @@ -1,7 +1,7 @@ import { TBTC as ContractTBTC } from "../../../typechain/TBTC" import { TBTCToken as ChainTBTCToken } from "../contracts" import { BigNumber, ContractTransaction, utils } from "ethers" -import { computeHash160, UnspentTransactionOutput } from "../bitcoin" +import { BitcoinHashUtils, BitcoinUtxo } from "../bitcoin" import { Hex } from "../utils" import { ContractConfig, @@ -38,7 +38,7 @@ export class TBTCToken */ async requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber ): Promise { @@ -69,7 +69,7 @@ export class TBTCToken private buildRequestRedemptionData( redeemer: Address, walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string ): Hex { const { @@ -99,10 +99,12 @@ export class TBTCToken private buildBridgeRequestRedemptionData( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string ) { - const walletPublicKeyHash = `0x${computeHash160(walletPublicKey)}` + const walletPublicKeyHash = `0x${BitcoinHashUtils.computeHash160( + walletPublicKey + )}` const mainUtxoParam = { // The Ethereum Bridge expects this hash to be in the Bitcoin internal diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index d381c2973..30c7eccec 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -8,7 +8,7 @@ import { OptimisticMintingRequestedEvent, } from "../contracts" import { BigNumber, ContractTransaction } from "ethers" -import { TransactionHash } from "../bitcoin" +import { BitcoinTxHash } from "../bitcoin" import { backoffRetrier, Hex } from "../utils" import TBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import { @@ -88,7 +88,7 @@ export class TBTCVault * @see {ChainTBTCVault#requestOptimisticMint} */ async requestOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { const tx = await sendWithRetry( @@ -114,7 +114,7 @@ export class TBTCVault * @see {ChainTBTCVault#cancelOptimisticMint} */ async cancelOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { const tx = await sendWithRetry( @@ -137,7 +137,7 @@ export class TBTCVault * @see {ChainTBTCVault#finalizeOptimisticMint} */ async finalizeOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { const tx = await sendWithRetry( @@ -163,7 +163,7 @@ export class TBTCVault * @see {ChainTBTCVault#optimisticMintingRequests} */ async optimisticMintingRequests( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) @@ -216,9 +216,7 @@ export class TBTCVault ), depositor: new Address(event.args!.depositor), amount: BigNumber.from(event.args!.amount), - fundingTxHash: TransactionHash.from( - event.args!.fundingTxHash - ).reverse(), + fundingTxHash: BitcoinTxHash.from(event.args!.fundingTxHash).reverse(), fundingOutputIndex: BigNumber.from( event.args!.fundingOutputIndex ).toNumber(), diff --git a/typescript/src/optimistic-minting.ts b/typescript/src/optimistic-minting.ts index d856b7b0e..3f884e8ab 100644 --- a/typescript/src/optimistic-minting.ts +++ b/typescript/src/optimistic-minting.ts @@ -1,4 +1,4 @@ -import { TransactionHash } from "./lib/bitcoin" +import { BitcoinTxHash } from "./lib/bitcoin" import { TBTCVault, OptimisticMintingRequest } from "./lib/contracts" import { Hex } from "./lib/utils" @@ -11,7 +11,7 @@ import { Hex } from "./lib/utils" * @returns Transaction hash of the optimistic mint request transaction. */ export async function requestOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number, tbtcVault: TBTCVault ): Promise { @@ -30,7 +30,7 @@ export async function requestOptimisticMint( * @returns Transaction hash of the optimistic mint cancel transaction. */ export async function cancelOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number, tbtcVault: TBTCVault ): Promise { @@ -46,7 +46,7 @@ export async function cancelOptimisticMint( * @returns Transaction hash of the optimistic mint finalize transaction. */ export async function finalizeOptimisticMint( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number, tbtcVault: TBTCVault ): Promise { @@ -65,7 +65,7 @@ export async function finalizeOptimisticMint( * @returns Optimistic minting request. */ export async function getOptimisticMintingRequest( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number, tbtcVault: TBTCVault ): Promise { diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index 8db6dd296..cda5658f2 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -1,14 +1,14 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { - assembleTransactionProof, + assembleBitcoinSpvProof, BitcoinNetwork, - createKeyRing, - decomposeRawTransaction, - RawTransaction, - UnspentTransactionOutput, - Client as BitcoinClient, - TransactionHash, + BitcoinPrivateKeyUtils, + extractBitcoinRawTxVectors, + BitcoinRawTx, + BitcoinUtxo, + BitcoinClient, + BitcoinTxHash, } from "./lib/bitcoin" import { Bridge, @@ -34,7 +34,7 @@ import { Hex } from "./lib/utils" */ export async function requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber, tBTCToken: TBTCToken @@ -73,25 +73,27 @@ export async function submitRedemptionTransaction( bitcoinClient: BitcoinClient, bridge: Bridge, walletPrivateKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScripts: string[], witness: boolean ): Promise<{ - transactionHash: TransactionHash - newMainUtxo?: UnspentTransactionOutput + transactionHash: BitcoinTxHash + newMainUtxo?: BitcoinUtxo }> { const mainUtxoRawTransaction = await bitcoinClient.getRawTransaction( mainUtxo.transactionHash ) - const mainUtxoWithRaw: UnspentTransactionOutput & RawTransaction = { + const mainUtxoWithRaw: BitcoinUtxo & BitcoinRawTx = { ...mainUtxo, transactionHex: mainUtxoRawTransaction.transactionHex, } const redemptionRequests = await getWalletRedemptionRequests( bridge, - createKeyRing(walletPrivateKey).getPublicKey().toString("hex"), + BitcoinPrivateKeyUtils.createKeyRing(walletPrivateKey) + .getPublicKey() + .toString("hex"), redeemerOutputScripts, "pending" ) @@ -204,19 +206,22 @@ async function getWalletRedemptionRequests( */ export async function assembleRedemptionTransaction( walletPrivateKey: string, - mainUtxo: UnspentTransactionOutput & RawTransaction, + mainUtxo: BitcoinUtxo & BitcoinRawTx, redemptionRequests: RedemptionRequest[], witness: boolean ): Promise<{ - transactionHash: TransactionHash - newMainUtxo?: UnspentTransactionOutput - rawTransaction: RawTransaction + transactionHash: BitcoinTxHash + newMainUtxo?: BitcoinUtxo + rawTransaction: BitcoinRawTx }> { if (redemptionRequests.length < 1) { throw new Error("There must be at least one request to redeem") } - const walletKeyRing = createKeyRing(walletPrivateKey, witness) + const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( + walletPrivateKey, + witness + ) const walletAddress = walletKeyRing.getAddress("string") // Use the main UTXO as the single transaction input @@ -283,7 +288,7 @@ export async function assembleRedemptionTransaction( transaction.sign(walletKeyRing) - const transactionHash = TransactionHash.from(transaction.txid()) + const transactionHash = BitcoinTxHash.from(transaction.txid()) // If there is a change output, it will be the new wallet's main UTXO. const newMainUtxo = changeOutputValue.gt(0) ? { @@ -315,14 +320,14 @@ export async function assembleRedemptionTransaction( * @returns Empty promise. */ export async function submitRedemptionProof( - transactionHash: TransactionHash, - mainUtxo: UnspentTransactionOutput, + transactionHash: BitcoinTxHash, + mainUtxo: BitcoinUtxo, walletPublicKey: string, bridge: Bridge, bitcoinClient: BitcoinClient ): Promise { const confirmations = await bridge.txProofDifficultyFactor() - const proof = await assembleTransactionProof( + const proof = await assembleBitcoinSpvProof( transactionHash, confirmations, bitcoinClient @@ -331,10 +336,10 @@ export async function submitRedemptionProof( // proof to the decomposed transaction data (version, inputs, outputs, locktime). // Use raw transaction data for now. const rawTransaction = await bitcoinClient.getRawTransaction(transactionHash) - const decomposedRawTransaction = decomposeRawTransaction(rawTransaction) + const rawTransactionVectors = extractBitcoinRawTxVectors(rawTransaction) await bridge.submitRedemptionProof( - decomposedRawTransaction, + rawTransactionVectors, proof, mainUtxo, walletPublicKey @@ -394,14 +399,14 @@ export async function findWalletForRedemption( bitcoinClient: BitcoinClient ): Promise<{ walletPublicKey: string - mainUtxo: UnspentTransactionOutput + mainUtxo: BitcoinUtxo }> { const wallets = await bridge.getNewWalletRegisteredEvents() let walletData: | { walletPublicKey: string - mainUtxo: UnspentTransactionOutput + mainUtxo: BitcoinUtxo } | undefined = undefined let maxAmount = BigNumber.from(0) diff --git a/typescript/src/wallet.ts b/typescript/src/wallet.ts index 6903042ff..8b2f0a6d5 100644 --- a/typescript/src/wallet.ts +++ b/typescript/src/wallet.ts @@ -1,12 +1,11 @@ import { Hex } from "./lib/utils" import { Bridge } from "./lib/contracts" import { - Client as BitcoinClient, + BitcoinClient, BitcoinNetwork, - createOutputScriptFromAddress, - encodeToBitcoinAddress, - TransactionOutput, - UnspentTransactionOutput, + BitcoinAddressConverter, + BitcoinTxOutput, + BitcoinUtxo, } from "./lib/bitcoin" /** @@ -30,7 +29,7 @@ export async function determineWalletMainUtxo( bridge: Bridge, bitcoinClient: BitcoinClient, bitcoinNetwork: BitcoinNetwork -): Promise { +): Promise { const { mainUtxoHash } = await bridge.wallets(walletPublicKeyHash) // Valid case when the wallet doesn't have a main UTXO registered into @@ -49,9 +48,9 @@ export async function determineWalletMainUtxo( // the given wallet address type. const determine = async ( witnessAddress: boolean - ): Promise => { + ): Promise => { // Build the wallet Bitcoin address based on its public key hash. - const walletAddress = encodeToBitcoinAddress( + const walletAddress = BitcoinAddressConverter.publicKeyHashToAddress( walletPublicKeyHash.toString(), witnessAddress, bitcoinNetwork @@ -74,11 +73,9 @@ export async function determineWalletMainUtxo( // Get the wallet script based on the wallet address. This is required // to find transaction outputs that lock funds on the wallet. - const walletScript = createOutputScriptFromAddress( - walletAddress, - bitcoinNetwork - ) - const isWalletOutput = (output: TransactionOutput) => + const walletScript = + BitcoinAddressConverter.addressToOutputScript(walletAddress) + const isWalletOutput = (output: BitcoinTxOutput) => walletScript.equals(output.scriptPubKey) // Start iterating from the latest transaction as the chance it matches @@ -100,7 +97,7 @@ export async function determineWalletMainUtxo( } // Build a candidate UTXO instance based on the detected output. - const utxo: UnspentTransactionOutput = { + const utxo: BitcoinUtxo = { transactionHash: walletTransaction.transactionHash, outputIndex: outputIndex, value: walletTransaction.outputs[outputIndex].value, diff --git a/typescript/test/bitcoin-network.test.ts b/typescript/test/bitcoin-network.test.ts index 92c258d53..aa87a5c0c 100644 --- a/typescript/test/bitcoin-network.test.ts +++ b/typescript/test/bitcoin-network.test.ts @@ -1,6 +1,6 @@ import { expect } from "chai" import { - TransactionHash, + BitcoinTxHash, BitcoinNetwork, toBcoinNetwork, } from "../src/lib/bitcoin" @@ -11,13 +11,13 @@ describe("BitcoinNetwork", () => { enumKey: BitcoinNetwork.Unknown, enumValue: "unknown", // any value that doesn't match other supported networks - genesisHash: TransactionHash.from("0x00010203"), + genesisHash: BitcoinTxHash.from("0x00010203"), expectedToBcoinResult: new Error("network not supported"), }, { enumKey: BitcoinNetwork.Testnet, enumValue: "testnet", - genesisHash: TransactionHash.from( + genesisHash: BitcoinTxHash.from( "0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" ), expectedToBcoinResult: "testnet", @@ -25,7 +25,7 @@ describe("BitcoinNetwork", () => { { enumKey: BitcoinNetwork.Mainnet, enumValue: "mainnet", - genesisHash: TransactionHash.from( + genesisHash: BitcoinTxHash.from( "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" ), expectedToBcoinResult: "main", diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index dcaf31853..7310d28b6 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -1,22 +1,14 @@ import { expect } from "chai" import { BitcoinNetwork, - compressPublicKey, - encodeToBitcoinAddress, - decodeBitcoinAddress, - isPublicKeyHashLength, - locktimeToNumber, - BlockHeader, - serializeBlockHeader, - deserializeBlockHeader, - hashLEToBigNumber, - bitsToTarget, - targetToDifficulty, - createOutputScriptFromAddress, - createAddressFromOutputScript, - readCompactSizeUint, - computeHash160, - computeHash256, + BitcoinPublicKeyUtils, + BitcoinLocktimeUtils, + BitcoinHeader, + BitcoinHeaderSerializer, + BitcoinHashUtils, + BitcoinTargetConverter, + BitcoinCompactSizeUint, + BitcoinAddressConverter, } from "../src/lib/bitcoin" import { calculateDepositRefundLocktime } from "../src/deposit" import { Hex } from "../src/lib/utils" @@ -24,89 +16,117 @@ import { BigNumber } from "ethers" import { btcAddresses } from "./data/bitcoin" describe("Bitcoin", () => { - describe("compressPublicKey", () => { - context("when public key parameter has a correct length", () => { - context("when the Y coordinate is divisible by 2", () => { - it("should compress the public key correctly", () => { - const uncompressedPublicKey = - "ff6e1857db52d6dba2bd4239fba722655622bc520709d38011f9adac8ea3477b" + - "45ae275b657f7bac7c1e3d146a564051aee1356895f01e4f29f333502416fa4a" - const compressedPublicKey = - "02ff6e1857db52d6dba2bd4239fba722655622bc520709d38011f9adac8ea3477b" + describe("BitcoinPublicKeyUtils", () => { + const { compressPublicKey } = BitcoinPublicKeyUtils + + describe("compressPublicKey", () => { + context("when public key parameter has a correct length", () => { + context("when the Y coordinate is divisible by 2", () => { + it("should compress the public key correctly", () => { + const uncompressedPublicKey = + "ff6e1857db52d6dba2bd4239fba722655622bc520709d38011f9adac8ea3477b" + + "45ae275b657f7bac7c1e3d146a564051aee1356895f01e4f29f333502416fa4a" + const compressedPublicKey = + "02ff6e1857db52d6dba2bd4239fba722655622bc520709d38011f9adac8ea3477b" + + expect(compressPublicKey(uncompressedPublicKey)).to.be.equal( + compressedPublicKey + ) + }) + }) - expect(compressPublicKey(uncompressedPublicKey)).to.be.equal( - compressedPublicKey - ) + context("when the Y coordinate is not divisible by 2", () => { + it("should compress the public key correctly", () => { + const uncompressedPublicKey = + "474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + + "7b5dff055ee1cc3a1fff4715dea2858ca4dd5bba0af30abcd881a6bda4fb70af" + const compressedPublicKey = + "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + + expect(compressPublicKey(uncompressedPublicKey)).to.be.equal( + compressedPublicKey + ) + }) }) }) - context("when the Y coordinate is not divisible by 2", () => { - it("should compress the public key correctly", () => { + context("when public key parameter has an incorrect length", () => { + it("should throw", () => { const uncompressedPublicKey = - "474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + + "04474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + "7b5dff055ee1cc3a1fff4715dea2858ca4dd5bba0af30abcd881a6bda4fb70af" - const compressedPublicKey = - "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" - expect(compressPublicKey(uncompressedPublicKey)).to.be.equal( - compressedPublicKey + expect(() => compressPublicKey(uncompressedPublicKey)).to.throw( + "The public key parameter must be 64-byte. Neither 0x nor 04 prefix is allowed" ) }) }) }) + }) - context("when public key parameter has an incorrect length", () => { - it("should throw", () => { - const uncompressedPublicKey = - "04474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + - "7b5dff055ee1cc3a1fff4715dea2858ca4dd5bba0af30abcd881a6bda4fb70af" + describe("BitcoinHashUtils", () => { + const { computeHash160, computeHash256, hashLEToBigNumber } = + BitcoinHashUtils - expect(() => compressPublicKey(uncompressedPublicKey)).to.throw( - "The public key parameter must be 64-byte. Neither 0x nor 04 prefix is allowed" - ) + describe("computeHash160", () => { + it("should compute hash160 correctly", () => { + const compressedPublicKey = + "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + const expectedHash160 = "3e1dfbd72483fb3964ca828ee71cf3270cafdc65" + + expect(computeHash160(compressedPublicKey)).to.be.equal(expectedHash160) }) }) - }) - describe("computeHash160", () => { - it("should compute hash160 correctly", () => { - const compressedPublicKey = - "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" - const expectedHash160 = "3e1dfbd72483fb3964ca828ee71cf3270cafdc65" + describe("computeHash256", () => { + it("should compute hash256 correctly", () => { + const hexValue = Hex.from( + "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + ) + const expectedHash256 = Hex.from( + "9f0b7447ca6ea11b8badd8a60a4dec1b846451551ef455975b1720f52bc90546" + ) - expect(computeHash160(compressedPublicKey)).to.be.equal(expectedHash160) + expect(computeHash256(hexValue).toString()).to.be.equal( + expectedHash256.toString() + ) + }) }) - }) - - describe("computeHash256", () => { - it("should compute hash256 correctly", () => { - const hexValue = Hex.from( - "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" - ) - const expectedHash256 = Hex.from( - "9f0b7447ca6ea11b8badd8a60a4dec1b846451551ef455975b1720f52bc90546" - ) - expect(computeHash256(hexValue).toString()).to.be.equal( - expectedHash256.toString() - ) + describe("hashLEToBigNumber", () => { + it("calculates correct value", () => { + const hash = Hex.from( + "31552151fbef8e96a33f979e6253d29edf65ac31b04802319e00000000000000" + ) + const expectedBigNumber = BigNumber.from( + "992983769452983078390935942095592601503357651673709518345521" + ) + expect(hashLEToBigNumber(hash)).to.equal(expectedBigNumber) + }) }) }) - describe("P2PKH <-> public key hash conversion", () => { + describe("BitcoinAddressConverter", () => { const publicKeyHash = "3a38d44d6a0c8d0bb84e0232cc632b7e48c72e0e" const P2WPKHAddress = "bc1q8gudgnt2pjxshwzwqgevccet0eyvwtswt03nuy" const P2PKHAddress = "16JrGhLx5bcBSA34kew9V6Mufa4aXhFe9X" const P2WPKHAddressTestnet = "tb1q8gudgnt2pjxshwzwqgevccet0eyvwtswpf2q8h" const P2PKHAddressTestnet = "mkpoZkRvtd3SDGWgUDuXK1aEXZfHRM2gKw" - describe("encodeToBitcoinAddress", () => { + const { + publicKeyHashToAddress, + addressToPublicKeyHash, + addressToOutputScript, + outputScriptToAddress, + } = BitcoinAddressConverter + + describe("publicKeyHashToAddress", () => { context("when network is mainnet", () => { context("when witness option is true", () => { context("when proper public key hash is provided", () => { it("should encode public key hash into bitcoin address properly", () => { expect( - encodeToBitcoinAddress( + publicKeyHashToAddress( publicKeyHash, true, BitcoinNetwork.Mainnet @@ -120,7 +140,7 @@ describe("Bitcoin", () => { const wrongPublicKeyHash = "02" + publicKeyHash expect(() => - encodeToBitcoinAddress( + publicKeyHashToAddress( wrongPublicKeyHash, true, BitcoinNetwork.Mainnet @@ -134,7 +154,7 @@ describe("Bitcoin", () => { context("when proper public key hash is provided", () => { it("should encode public key hash into bitcoin address properly", () => { expect( - encodeToBitcoinAddress( + publicKeyHashToAddress( publicKeyHash, false, BitcoinNetwork.Mainnet @@ -148,7 +168,7 @@ describe("Bitcoin", () => { const wrongPublicKeyHash = "02" + publicKeyHash expect(() => - encodeToBitcoinAddress( + publicKeyHashToAddress( wrongPublicKeyHash, false, BitcoinNetwork.Mainnet @@ -164,7 +184,7 @@ describe("Bitcoin", () => { context("when proper public key hash is provided", () => { it("should encode public key hash into bitcoin address properly", () => { expect( - encodeToBitcoinAddress( + publicKeyHashToAddress( publicKeyHash, true, BitcoinNetwork.Testnet @@ -178,7 +198,7 @@ describe("Bitcoin", () => { const wrongPublicKeyHash = "02" + publicKeyHash expect(() => - encodeToBitcoinAddress( + publicKeyHashToAddress( wrongPublicKeyHash, true, BitcoinNetwork.Testnet @@ -192,7 +212,7 @@ describe("Bitcoin", () => { context("when proper public key hash is provided", () => { it("should encode public key hash into bitcoin address properly", () => { expect( - encodeToBitcoinAddress( + publicKeyHashToAddress( publicKeyHash, false, BitcoinNetwork.Testnet @@ -206,7 +226,7 @@ describe("Bitcoin", () => { const wrongPublicKeyHash = "02" + publicKeyHash expect(() => - encodeToBitcoinAddress( + publicKeyHashToAddress( wrongPublicKeyHash, false, BitcoinNetwork.Testnet @@ -220,17 +240,17 @@ describe("Bitcoin", () => { context("when network is unknown", () => { it("should throw", () => { expect(() => - encodeToBitcoinAddress(publicKeyHash, true, BitcoinNetwork.Unknown) + publicKeyHashToAddress(publicKeyHash, true, BitcoinNetwork.Unknown) ).to.throw("network not supported") }) }) }) - describe("decodeAddress", () => { + describe("addressToPublicKeyHash", () => { context("when network is mainnet", () => { context("when proper P2WPKH address is provided", () => { it("should decode P2WPKH adress correctly", () => { - expect(decodeBitcoinAddress(P2WPKHAddress)).to.be.equal( + expect(addressToPublicKeyHash(P2WPKHAddress)).to.be.equal( publicKeyHash ) }) @@ -238,7 +258,7 @@ describe("Bitcoin", () => { context("when proper P2PKH address is provided", () => { it("should decode P2PKH address correctly", () => { - expect(decodeBitcoinAddress(P2PKHAddress)).to.be.equal( + expect(addressToPublicKeyHash(P2PKHAddress)).to.be.equal( publicKeyHash ) }) @@ -248,7 +268,7 @@ describe("Bitcoin", () => { it("should throw", () => { const bitcoinAddress = "123" + P2PKHAddress - expect(() => decodeBitcoinAddress(bitcoinAddress)).to.throw( + expect(() => addressToPublicKeyHash(bitcoinAddress)).to.throw( "Address is too long" ) }) @@ -257,7 +277,7 @@ describe("Bitcoin", () => { context("when unsupported P2SH address is provided", () => { it("should throw", () => { expect(() => - decodeBitcoinAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX") + addressToPublicKeyHash("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX") ).to.throw("Address must be P2PKH or P2WPKH") }) }) @@ -265,7 +285,7 @@ describe("Bitcoin", () => { context("when unsupported P2WSH address is provided", () => { it("should throw", () => { expect(() => - decodeBitcoinAddress( + addressToPublicKeyHash( "bc1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhsdxuv4m" ) ).to.throw("Address must be P2PKH or P2WPKH") @@ -276,7 +296,7 @@ describe("Bitcoin", () => { context("when network is testnet", () => { context("when proper P2WPKH address is provided", () => { it("should decode P2WPKH adress correctly", () => { - expect(decodeBitcoinAddress(P2WPKHAddressTestnet)).to.be.equal( + expect(addressToPublicKeyHash(P2WPKHAddressTestnet)).to.be.equal( publicKeyHash ) }) @@ -284,7 +304,7 @@ describe("Bitcoin", () => { context("when proper P2PKH address is provided", () => { it("should decode P2PKH address correctly", () => { - expect(decodeBitcoinAddress(P2PKHAddressTestnet)).to.be.equal( + expect(addressToPublicKeyHash(P2PKHAddressTestnet)).to.be.equal( publicKeyHash ) }) @@ -294,7 +314,7 @@ describe("Bitcoin", () => { it("should throw", () => { const bitcoinAddress = "123" + P2PKHAddressTestnet - expect(() => decodeBitcoinAddress(bitcoinAddress)).to.throw( + expect(() => addressToPublicKeyHash(bitcoinAddress)).to.throw( "Address is too long" ) }) @@ -303,7 +323,7 @@ describe("Bitcoin", () => { context("when unsupported P2SH address is provided", () => { it("should throw", () => { expect(() => - decodeBitcoinAddress("2MyxShnGQ5NifGb8CHYrtmzosRySxZ9pZo5") + addressToPublicKeyHash("2MyxShnGQ5NifGb8CHYrtmzosRySxZ9pZo5") ).to.throw("Address must be P2PKH or P2WPKH") }) }) @@ -311,7 +331,7 @@ describe("Bitcoin", () => { context("when unsupported P2WSH address is provided", () => { it("should throw", () => { expect(() => - decodeBitcoinAddress( + addressToPublicKeyHash( "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05" ) ).to.throw("Address must be P2PKH or P2WPKH") @@ -319,255 +339,245 @@ describe("Bitcoin", () => { }) }) }) - }) - - describe("isPublicKeyHashLength", () => { - const publicKeyHash = "3a38d44d6a0c8d0bb84e0232cc632b7e48c72e0e" - const wrongPublicKeyHash = "3a38d44d6a0c8d0" - context("when proper public key hash is provided", () => { - it("should return true", () => { - expect(isPublicKeyHashLength(publicKeyHash)).to.be.equal(true) + describe("addressToOutputScript", () => { + Object.keys(btcAddresses).forEach((bitcoinNetwork) => { + context(`with ${bitcoinNetwork} addresses`, () => { + Object.entries( + btcAddresses[bitcoinNetwork as keyof typeof btcAddresses] + ).forEach( + ([ + addressType, + { address, scriptPubKey: expectedOutputScript }, + ]) => { + it(`should create correct output script for ${addressType} address type`, () => { + const result = addressToOutputScript(address) + + expect(result.toString()).to.eq(expectedOutputScript.toString()) + }) + } + ) + }) }) }) - context("when wrong public key hash is provided", () => { - it("should return false", () => { - expect(isPublicKeyHashLength(wrongPublicKeyHash)).to.be.equal(false) + describe("outputScriptToAddress", () => { + Object.keys(btcAddresses).forEach((bitcoinNetwork) => { + context(`with ${bitcoinNetwork} addresses`, () => { + Object.entries( + btcAddresses[bitcoinNetwork as keyof typeof btcAddresses] + ).forEach(([addressType, { address, scriptPubKey }]) => { + it(`should return correct ${addressType} address`, () => { + const result = outputScriptToAddress( + scriptPubKey, + bitcoinNetwork === "mainnet" + ? BitcoinNetwork.Mainnet + : BitcoinNetwork.Testnet + ) + + expect(result.toString()).to.eq(address) + }) + }) + }) }) }) }) - describe("locktimeToNumber", () => { - const depositCreatedAt: number = 1640181600 - const depositRefundLocktimeDuration: number = 2592000 - const depositRefundLocktime = calculateDepositRefundLocktime( - depositCreatedAt, - depositRefundLocktimeDuration - ) - - const testData = [ - { - contextName: "when locktime is a block height", - unprefixedHex: "ede80600", - expectedDepositLocktime: 452845, - }, - { - contextName: "when locktime is a timestamp", - unprefixedHex: "06241559", - expectedDepositLocktime: 1494557702, - }, - { - contextName: "for deposit refund locktime", - unprefixedHex: depositRefundLocktime, - expectedDepositLocktime: - depositCreatedAt + depositRefundLocktimeDuration, - }, - ] - - testData.forEach((test) => { - context(test.contextName, () => { - context("when input is non-prefixed hex string", () => { - it("should return the locktime in seconds", async () => { - expect(locktimeToNumber(test.unprefixedHex)).to.be.equal( - test.expectedDepositLocktime - ) + describe("BitcoinLocktimeUtils", () => { + const { locktimeToNumber } = BitcoinLocktimeUtils + + describe("locktimeToNumber", () => { + const depositCreatedAt: number = 1640181600 + const depositRefundLocktimeDuration: number = 2592000 + const depositRefundLocktime = calculateDepositRefundLocktime( + depositCreatedAt, + depositRefundLocktimeDuration + ) + + const testData = [ + { + contextName: "when locktime is a block height", + unprefixedHex: "ede80600", + expectedDepositLocktime: 452845, + }, + { + contextName: "when locktime is a timestamp", + unprefixedHex: "06241559", + expectedDepositLocktime: 1494557702, + }, + { + contextName: "for deposit refund locktime", + unprefixedHex: depositRefundLocktime, + expectedDepositLocktime: + depositCreatedAt + depositRefundLocktimeDuration, + }, + ] + + testData.forEach((test) => { + context(test.contextName, () => { + context("when input is non-prefixed hex string", () => { + it("should return the locktime in seconds", async () => { + expect(locktimeToNumber(test.unprefixedHex)).to.be.equal( + test.expectedDepositLocktime + ) + }) }) - }) - context("when input is 0x prefixed hex string", () => { - it("should return the locktime in seconds", async () => { - expect(locktimeToNumber("0x" + test.unprefixedHex)).to.be.equal( - test.expectedDepositLocktime - ) + context("when input is 0x prefixed hex string", () => { + it("should return the locktime in seconds", async () => { + expect(locktimeToNumber("0x" + test.unprefixedHex)).to.be.equal( + test.expectedDepositLocktime + ) + }) }) - }) - context("when input is Buffer object", () => { - it("should return the locktime in seconds", async () => { - expect( - locktimeToNumber(Buffer.from(test.unprefixedHex, "hex")) - ).to.be.equal(test.expectedDepositLocktime) + context("when input is Buffer object", () => { + it("should return the locktime in seconds", async () => { + expect( + locktimeToNumber(Buffer.from(test.unprefixedHex, "hex")) + ).to.be.equal(test.expectedDepositLocktime) + }) }) }) }) }) }) - describe("serializeBlockHeader", () => { - it("calculates correct value", () => { - const blockHeader: BlockHeader = { - version: 536870916, - previousBlockHeaderHash: Hex.from( - "a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef300045660000000000" - ), - merkleRootHash: Hex.from( - "e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b49649775112" - ), - time: 1641914003, - bits: 436256810, - nonce: 778087099, - } - - const expectedSerializedBlockHeader = Hex.from( - "04000020a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef30004566000000" + - "0000e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b496497751" + - "12939edd612ac0001abbaa602e" - ) + describe("BitcoinHeaderSerializer", () => { + const { serializeHeader, deserializeHeader } = BitcoinHeaderSerializer + + describe("serializeHeader", () => { + it("calculates correct value", () => { + const blockHeader: BitcoinHeader = { + version: 536870916, + previousBlockHeaderHash: Hex.from( + "a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef300045660000000000" + ), + merkleRootHash: Hex.from( + "e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b49649775112" + ), + time: 1641914003, + bits: 436256810, + nonce: 778087099, + } + + const expectedSerializedBlockHeader = Hex.from( + "04000020a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef30004566000000" + + "0000e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b496497751" + + "12939edd612ac0001abbaa602e" + ) - expect(serializeBlockHeader(blockHeader)).to.be.deep.equal( - expectedSerializedBlockHeader - ) + expect(serializeHeader(blockHeader)).to.be.deep.equal( + expectedSerializedBlockHeader + ) + }) }) - }) - - describe("deserializeBlockHeader", () => { - it("calculates correct value", () => { - const rawBlockHeader = Hex.from( - "04000020a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef30004566000000" + - "0000e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b496497751" + - "12939edd612ac0001abbaa602e" - ) - const expectedBlockHeader: BlockHeader = { - version: 536870916, - previousBlockHeaderHash: Hex.from( - "a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef300045660000000000" - ), - merkleRootHash: Hex.from( - "e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b49649775112" - ), - time: 1641914003, - bits: 436256810, - nonce: 778087099, - } - - expect(deserializeBlockHeader(rawBlockHeader)).to.deep.equal( - expectedBlockHeader - ) - }) - }) + describe("deserializeHeader", () => { + it("calculates correct value", () => { + const rawBlockHeader = Hex.from( + "04000020a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef30004566000000" + + "0000e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b496497751" + + "12939edd612ac0001abbaa602e" + ) - describe("hashLEToBigNumber", () => { - it("calculates correct value", () => { - const hash = Hex.from( - "31552151fbef8e96a33f979e6253d29edf65ac31b04802319e00000000000000" - ) - const expectedBigNumber = BigNumber.from( - "992983769452983078390935942095592601503357651673709518345521" - ) - expect(hashLEToBigNumber(hash)).to.equal(expectedBigNumber) + const expectedBlockHeader: BitcoinHeader = { + version: 536870916, + previousBlockHeaderHash: Hex.from( + "a5a3501e6ba1f3e2a1ee5d29327a549524ed33f272dfef300045660000000000" + ), + merkleRootHash: Hex.from( + "e27d241ca36de831ab17e6729056c14a383e7a3f43d56254f846b49649775112" + ), + time: 1641914003, + bits: 436256810, + nonce: 778087099, + } + + expect(deserializeHeader(rawBlockHeader)).to.deep.equal( + expectedBlockHeader + ) + }) }) }) - describe("bitsToTarget", () => { - it("calculates correct value for random block header bits", () => { - const difficultyBits = 436256810 - const expectedDifficultyTarget = BigNumber.from( - "1206233370197704583969288378458116959663044038027202007138304" - ) - expect(bitsToTarget(difficultyBits)).to.equal(expectedDifficultyTarget) - }) + describe("BitcoinTargetConverter", () => { + const { bitsToTarget, targetToDifficulty } = BitcoinTargetConverter - it("calculates correct value for block header with difficulty of 1", () => { - const difficultyBits = 486604799 - const expectedDifficultyTarget = BigNumber.from( - "26959535291011309493156476344723991336010898738574164086137773096960" - ) - expect(bitsToTarget(difficultyBits)).to.equal(expectedDifficultyTarget) - }) - }) + describe("bitsToTarget", () => { + it("calculates correct value for random block header bits", () => { + const difficultyBits = 436256810 + const expectedDifficultyTarget = BigNumber.from( + "1206233370197704583969288378458116959663044038027202007138304" + ) + expect(bitsToTarget(difficultyBits)).to.equal(expectedDifficultyTarget) + }) - describe("targetToDifficulty", () => { - it("calculates correct value for random block header bits", () => { - const target = BigNumber.from( - "1206233370197704583969288378458116959663044038027202007138304" - ) - const expectedDifficulty = BigNumber.from("22350181") - expect(targetToDifficulty(target)).to.equal(expectedDifficulty) + it("calculates correct value for block header with difficulty of 1", () => { + const difficultyBits = 486604799 + const expectedDifficultyTarget = BigNumber.from( + "26959535291011309493156476344723991336010898738574164086137773096960" + ) + expect(bitsToTarget(difficultyBits)).to.equal(expectedDifficultyTarget) + }) }) - it("calculates correct value for block header with difficulty of 1", () => { - const target = BigNumber.from( - "26959535291011309493156476344723991336010898738574164086137773096960" - ) - const expectedDifficulty = BigNumber.from("1") - expect(targetToDifficulty(target)).to.equal(expectedDifficulty) - }) - }) + describe("targetToDifficulty", () => { + it("calculates correct value for random block header bits", () => { + const target = BigNumber.from( + "1206233370197704583969288378458116959663044038027202007138304" + ) + const expectedDifficulty = BigNumber.from("22350181") + expect(targetToDifficulty(target)).to.equal(expectedDifficulty) + }) - describe("createOutputScriptFromAddress", () => { - Object.keys(btcAddresses).forEach((bitcoinNetwork) => { - context(`with ${bitcoinNetwork} addresses`, () => { - Object.entries( - btcAddresses[bitcoinNetwork as keyof typeof btcAddresses] - ).forEach( - ([addressType, { address, scriptPubKey: expectedOutputScript }]) => { - it(`should create correct output script for ${addressType} address type`, () => { - const result = createOutputScriptFromAddress(address) - - expect(result.toString()).to.eq(expectedOutputScript.toString()) - }) - } + it("calculates correct value for block header with difficulty of 1", () => { + const target = BigNumber.from( + "26959535291011309493156476344723991336010898738574164086137773096960" ) + const expectedDifficulty = BigNumber.from("1") + expect(targetToDifficulty(target)).to.equal(expectedDifficulty) }) }) }) - describe("getAddressFromScriptPubKey", () => { - Object.keys(btcAddresses).forEach((bitcoinNetwork) => { - context(`with ${bitcoinNetwork} addresses`, () => { - Object.entries( - btcAddresses[bitcoinNetwork as keyof typeof btcAddresses] - ).forEach(([addressType, { address, scriptPubKey }]) => { - it(`should return correct ${addressType} address`, () => { - const result = createAddressFromOutputScript( - scriptPubKey, - bitcoinNetwork === "mainnet" - ? BitcoinNetwork.Mainnet - : BitcoinNetwork.Testnet - ) + describe("BitcoinCompactSizeUint", () => { + const { read } = BitcoinCompactSizeUint - expect(result.toString()).to.eq(address) + describe("read", () => { + context("when the compact size uint is 1-byte", () => { + it("should return the the uint value and byte length", () => { + expect(read(Hex.from("bb"))).to.be.eql({ + value: 187, + byteLength: 1, }) }) }) - }) - }) - describe("readCompactSizeUint", () => { - context("when the compact size uint is 1-byte", () => { - it("should return the the uint value and byte length", () => { - expect(readCompactSizeUint(Hex.from("bb"))).to.be.eql({ - value: 187, - byteLength: 1, + context("when the compact size uint is 3-byte", () => { + it("should throw", () => { + expect(() => read(Hex.from("fd0302"))).to.throw( + "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + ) }) }) - }) - context("when the compact size uint is 3-byte", () => { - it("should throw", () => { - expect(() => readCompactSizeUint(Hex.from("fd0302"))).to.throw( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" - ) - }) - }) - - context("when the compact size uint is 5-byte", () => { - it("should throw", () => { - expect(() => readCompactSizeUint(Hex.from("fe703a0f00"))).to.throw( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" - ) + context("when the compact size uint is 5-byte", () => { + it("should throw", () => { + expect(() => read(Hex.from("fe703a0f00"))).to.throw( + "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + ) + }) }) - }) - context("when the compact size uint is 9-byte", () => { - it("should throw", () => { - expect(() => { - return readCompactSizeUint(Hex.from("ff57284e56dab40000")) - }).to.throw( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" - ) + context("when the compact size uint is 9-byte", () => { + it("should throw", () => { + expect(() => { + return read(Hex.from("ff57284e56dab40000")) + }).to.throw( + "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + ) + }) }) }) }) diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index d5763354b..1d0da98f3 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -1,9 +1,5 @@ import { BigNumber } from "ethers" -import { - RawTransaction, - UnspentTransactionOutput, - TransactionHash, -} from "../../src/lib/bitcoin" +import { BitcoinRawTx, BitcoinUtxo, BitcoinTxHash } from "../../src/lib/bitcoin" import { Deposit } from "../../src/lib/contracts" import { calculateDepositRefundLocktime } from "../../src/deposit" import { Address } from "../../src/lib/ethereum" @@ -21,13 +17,13 @@ export const refunderPrivateKey = */ export interface DepositRefundTestData { deposit: { - utxo: UnspentTransactionOutput & RawTransaction + utxo: BitcoinUtxo & BitcoinRawTx data: Deposit } refunderAddress: string expectedRefund: { - transactionHash: TransactionHash - transaction: RawTransaction + transactionHash: BitcoinTxHash + transaction: BitcoinRawTx } } @@ -40,7 +36,7 @@ export const depositRefundOfWitnessDepositAndWitnessRefunderAddress: DepositRefu { deposit: { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "6430be26d8564658bf3ff0f74e4a7ddce9d65e9c7157d6e4a203125fc01d3c6d" ), outputIndex: 0, @@ -67,7 +63,7 @@ export const depositRefundOfWitnessDepositAndWitnessRefunderAddress: DepositRefu // witness address associated with the refunder's private key refunderAddress: "tb1qrdnlyafhc7es5g7cenhmj6jv4n789kdpw5kty9", expectedRefund: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "b49bd6c0219066f0c76d85818b047e4685425844cda42dae9b9508b9bfbb483d" ), transaction: { @@ -94,7 +90,7 @@ export const depositRefundOfNonWitnessDepositAndWitnessRefunderAddress: DepositR { deposit: { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "60650462f367bf89b5a0dc52d7d1f65986296fa8d8903b129c444e2b742f0143" ), outputIndex: 0, @@ -120,7 +116,7 @@ export const depositRefundOfNonWitnessDepositAndWitnessRefunderAddress: DepositR // witness address associated with the refunder's private key refunderAddress: "tb1qrdnlyafhc7es5g7cenhmj6jv4n789kdpw5kty9", expectedRefund: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "7df9ed885525899ccbe144fd129062cec59be43d428b85fb847808b8790ad262" ), transaction: { @@ -147,7 +143,7 @@ export const depositRefundOfWitnessDepositAndNonWitnessRefunderAddress: DepositR { deposit: { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "b1fb065a61a6401279cafb95d10b502a6cd22f747bcfdb09ab25d4ee6f64319f" ), outputIndex: 0, @@ -173,7 +169,7 @@ export const depositRefundOfWitnessDepositAndNonWitnessRefunderAddress: DepositR // non-witness address associated with the refunder's private key refunderAddress: "mi1s4c2GtyVpqQb6MEpMbKimq3mwu5Z3a6", expectedRefund: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "0400678f7ae0275338cb0418236960c04c016b980cb7d1763c1d957f534ae0eb" ), transaction: { diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index ba394fc4d..18e96b513 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -1,11 +1,11 @@ import { - DecomposedRawTransaction, - Proof, - Transaction, - RawTransaction, - UnspentTransactionOutput, - TransactionMerkleBranch, - TransactionHash, + BitcoinRawTxVectors, + BitcoinSpvProof, + BitcoinTx, + BitcoinRawTx, + BitcoinUtxo, + BitcoinTxMerkleBranch, + BitcoinTxHash, } from "../../src/lib/bitcoin" import { Deposit } from "../../src/lib/contracts" import { calculateDepositRefundLocktime } from "../../src/deposit" @@ -14,7 +14,7 @@ import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src" export const NO_MAIN_UTXO = { - transactionHash: TransactionHash.from(""), + transactionHash: BitcoinTxHash.from(""), outputIndex: 0, value: BigNumber.from(0), transactionHex: "", @@ -25,14 +25,14 @@ export const NO_MAIN_UTXO = { */ export interface DepositSweepTestData { deposits: { - utxo: UnspentTransactionOutput & RawTransaction + utxo: BitcoinUtxo & BitcoinRawTx data: Deposit }[] - mainUtxo: UnspentTransactionOutput & RawTransaction + mainUtxo: BitcoinUtxo & BitcoinRawTx witness: boolean expectedSweep: { - transactionHash: TransactionHash - transaction: RawTransaction + transactionHash: BitcoinTxHash + transaction: BitcoinRawTx } } @@ -46,7 +46,7 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = deposits: [ { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "74d0e353cdba99a6c17ce2cfeab62a26c09b5eb756eccdcfb83dbc12e67b18bc" ), outputIndex: 0, @@ -73,7 +73,7 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = }, { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "5c54ecdf946382fab2236f78423ddc22a757776fb8492671c588667b737e55dc" ), outputIndex: 0, @@ -103,7 +103,7 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = mainUtxo: NO_MAIN_UTXO, witness: true, expectedSweep: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f8eaf242a55ea15e602f9f990e33f67f99dfbe25d1802bbde63cc1caabf99668" ), transaction: { @@ -138,7 +138,7 @@ export const depositSweepWithNoMainUtxoAndNonWitnessOutput: DepositSweepTestData deposits: [ { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "4cdd899d7133cd681bdc4e80b3af09d34da1f7450c5b19167aa8a8223c7a8426" ), outputIndex: 0, @@ -161,7 +161,7 @@ export const depositSweepWithNoMainUtxoAndNonWitnessOutput: DepositSweepTestData mainUtxo: NO_MAIN_UTXO, witness: false, expectedSweep: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "1c42b0568d88bb4d21ae138769fd06199dd3ec689911972792e678be8516d58d" ), transaction: { @@ -183,7 +183,7 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa { // P2SH deposit utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "d4fe2ef9068d039eae2210e893db518280d4757696fe9db8f3c696a94de90aed" ), outputIndex: 0, @@ -211,7 +211,7 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa { // P2WSH deposit utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "b86ef64f8aff19778535e7c6e45ff82bc2f5f5eec800fd2b03a03fc22f557fe3" ), outputIndex: 0, @@ -239,7 +239,7 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa ], mainUtxo: { // P2WPKH - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f8eaf242a55ea15e602f9f990e33f67f99dfbe25d1802bbde63cc1caabf99668" ), outputIndex: 0, @@ -264,7 +264,7 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa }, witness: true, expectedSweep: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "435d4aff6d4bc34134877bd3213c17970142fdd04d4113d534120033b9eecb2e" ), transaction: { @@ -304,7 +304,7 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes deposits: [ { utxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "fda2323b4075a0311767ae8db07f4387bb53663a304278cd8c2c7a591f203a17" ), outputIndex: 0, @@ -332,7 +332,7 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes }, ], mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c8a2c407309b9434cb73d4788ce4ac895084240eec7bb440e7f76b75be1296e1" ), outputIndex: 0, @@ -350,24 +350,25 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes }, witness: true, expectedSweep: { - transactionHash: TransactionHash.from( - "1933781f01f27f086c3a31c4a53035ebc7c4688e1f4b316babefa8f6dab77dc2" + transactionHash: BitcoinTxHash.from( + "7831d0dfde7e160f3b9bb66c433710f0d3110d73ea78b9db65e81c091a6718a0" ), transaction: { transactionHex: - "01000000000102e19612be756bf7e740b47bec0e24845089ace48c78d473cb34949" + - "b3007c4a2c8000000006a473044022013787be70eda0620002fa55c92abfcd32257" + - "d64fa652dd32bac65d705162a95902203407fea1abc99a9273ead3179ce60f60a34" + - "33fb2e93f58569e4bee9f63c0d679012103989d253b17a6a0f41838b84ff0d20e88" + - "98f9d7b1a98f2564da4cc29dcf8581d9ffffffff173a201f597a2c8ccd7842303a6" + - "653bb87437fb08dae671731a075403b32a2fd0000000000ffffffff010884000000" + - "0000001600148db50eb52063ea9d98b3eac91489a90f738986f6000348304502210" + - "0804f0fa989d632cda99a24159e28b8d31d4033c2d5de47d8207ea2767273d10a02" + - "20278e82d0714867b31eb013762306e2b97c2c1cc74b8135bee78d565e72ee630e0" + - "12103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581" + - "d95c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d000395237" + - "576a9148db50eb52063ea9d98b3eac91489a90f738986f68763ac6776a914e257ec" + - "cafbc07c381642ce6e7e55120fb077fbed880448f2b262b175ac6800000000", + "01000000000102173a201f597a2c8ccd7842303a6653bb87437fb08dae671731a0" + + "75403b32a2fd0000000000ffffffffe19612be756bf7e740b47bec0e24845089ac" + + "e48c78d473cb34949b3007c4a2c8000000006a47304402204382deb051f9f3e2b5" + + "39e4bac2d1a50faf8d66bc7a3a3f3d286dabd96d92b58b02207c74c6aaf48e25d0" + + "7e02bb4039606d77ecfd80c492c050ab2486af6027fc2d5a012103989d253b17a6" + + "a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9ffffffff010884" + + "0000000000001600148db50eb52063ea9d98b3eac91489a90f738986f603483045" + + "022100c52bc876cdee80a3061ace3ffbce5e860942d444cd38e00e5f63fd8e818d" + + "7e7c022040a7017bb8213991697705e7092c481526c788a4731d06e582dc1c57be" + + "d7243b012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc2" + + "9dcf8581d95c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d" + + "000395237576a9148db50eb52063ea9d98b3eac91489a90f738986f68763ac6776" + + "a914e257eccafbc07c381642ce6e7e55120fb077fbed880448f2b262b175ac6800" + + "00000000", }, }, } @@ -377,17 +378,17 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes */ export interface DepositSweepProofTestData { bitcoinChainData: { - transaction: Transaction - rawTransaction: RawTransaction + transaction: BitcoinTx + rawTransaction: BitcoinRawTx accumulatedTxConfirmations: number latestBlockHeight: number headersChain: string - transactionMerkleBranch: TransactionMerkleBranch + transactionMerkleBranch: BitcoinTxMerkleBranch } expectedSweepProof: { - sweepTx: DecomposedRawTransaction - sweepProof: Proof - mainUtxo: UnspentTransactionOutput + sweepTx: BitcoinRawTxVectors + sweepProof: BitcoinSpvProof + mainUtxo: BitcoinUtxo } } @@ -398,33 +399,33 @@ export interface DepositSweepProofTestData { export const depositSweepProof: DepositSweepProofTestData = { bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "5083822ed0b8d0bc661362b778e666cb572ff6d5152193992dd69d3207995753" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "ea4d9e45f8c1b8a187c007f36ba1e9b201e8511182c7083c4edcaf9325b2998f" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c844ff4c1781c884bb5e80392398b81b984d7106367ae16675f132bd1a7f33fd" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f548c00e464764e112826450a00cf005ca771a6108a629b559b6c60a519e4378" ), outputIndex: 0, diff --git a/typescript/test/data/deposit.ts b/typescript/test/data/deposit.ts index 42371740f..241c6e4bd 100644 --- a/typescript/test/data/deposit.ts +++ b/typescript/test/data/deposit.ts @@ -1,8 +1,4 @@ -import { - RawTransaction, - TransactionHash, - UnspentTransactionOutput, -} from "../../src/lib/bitcoin" +import { BitcoinRawTx, BitcoinTxHash, BitcoinUtxo } from "../../src/lib/bitcoin" import { BigNumber } from "ethers" /** @@ -20,7 +16,7 @@ export const testnetPrivateKey = /** * Hash of one of the transactions originating from testnetAddress. */ -export const testnetTransactionHash = TransactionHash.from( +export const testnetTransactionHash = BitcoinTxHash.from( "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883" ) @@ -29,7 +25,7 @@ export const testnetTransactionHash = TransactionHash.from( * from testnetAddress. It can be decoded, for example, with: * https://live.blockcypher.com/btc-testnet/decodetx */ -export const testnetTransaction: RawTransaction = { +export const testnetTransaction: BitcoinRawTx = { transactionHex: "0100000000010162cae24e74ad64f9f0493b09f3964908b3b3038f4924882d3dbd853b" + "4c9bc7390100000000ffffffff02102700000000000017a914867120d5480a9cc0c11c" + @@ -43,7 +39,7 @@ export const testnetTransaction: RawTransaction = { /** * An UTXO from the testnetTransaction. */ -export const testnetUTXO: UnspentTransactionOutput & RawTransaction = { +export const testnetUTXO: BitcoinUtxo & BitcoinRawTx = { transactionHash: testnetTransactionHash, outputIndex: 1, value: BigNumber.from(3933200), diff --git a/typescript/test/data/electrum.ts b/typescript/test/data/electrum.ts index ddc3f6a06..374fe9c14 100644 --- a/typescript/test/data/electrum.ts +++ b/typescript/test/data/electrum.ts @@ -1,9 +1,9 @@ import { - Transaction, - RawTransaction, - UnspentTransactionOutput, - TransactionMerkleBranch, - TransactionHash, + BitcoinTx, + BitcoinRawTx, + BitcoinUtxo, + BitcoinTxMerkleBranch, + BitcoinTxHash, } from "../../src/lib/bitcoin" import { BigNumber } from "ethers" import { Hex } from "../../src" @@ -17,14 +17,14 @@ export const testnetAddress: string = /** * A testnet transaction originating from {@link testnetAddress} */ -export const testnetTransaction: Transaction = { - transactionHash: TransactionHash.from( +export const testnetTransaction: BitcoinTx = { + transactionHash: BitcoinTxHash.from( "72e7fd57c2adb1ed2305c4247486ff79aec363296f02ec65be141904f80d214e" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c6ffe9e0f8cca057acad211023ff6b9d46604fbbcb76c6dd669c20b22985f802" ), outputIndex: 1, @@ -49,7 +49,7 @@ export const testnetTransaction: Transaction = { /** * Raw transaction corresponding to {@link testnetTransaction} */ -export const testnetRawTransaction: RawTransaction = { +export const testnetRawTransaction: BitcoinRawTx = { transactionHex: "0200000000010102f88529b2209c66ddc676cbbb4f60469d6bff231021adac57a0ccf8e" + "0e9ffc60100000000fdffffff0265000000000000001600144b47c798d12edd17dfb4ea" + @@ -63,7 +63,7 @@ export const testnetRawTransaction: RawTransaction = { /** * An UTXO being result of {@link testnetTransaction} */ -export const testnetUTXO: UnspentTransactionOutput = { +export const testnetUTXO: BitcoinUtxo = { transactionHash: testnetTransaction.transactionHash, outputIndex: 0, value: BigNumber.from(101), @@ -97,7 +97,7 @@ export const testnetHeadersChain = { /** * Transaction merkle branch corresponding to {@link testnetTransaction} */ -export const testnetTransactionMerkleBranch: TransactionMerkleBranch = { +export const testnetTransactionMerkleBranch: BitcoinTxMerkleBranch = { blockHeight: 1569342, merkle: [ "8b5bbb5bdf6727bf70fad4f46fe4eaab04c98119ffbd2d95c29adf32d26f8452", diff --git a/typescript/test/data/proof.ts b/typescript/test/data/proof.ts index ab3ae7942..ec212b497 100644 --- a/typescript/test/data/proof.ts +++ b/typescript/test/data/proof.ts @@ -1,9 +1,9 @@ import { - Proof, - RawTransaction, - Transaction, - TransactionHash, - TransactionMerkleBranch, + BitcoinSpvProof, + BitcoinRawTx, + BitcoinTx, + BitcoinTxHash, + BitcoinTxMerkleBranch, } from "../../src/lib/bitcoin" import { BigNumber } from "ethers" import { Hex } from "../../src" @@ -14,14 +14,14 @@ import { Hex } from "../../src" export interface ProofTestData { requiredConfirmations: number bitcoinChainData: { - transaction: Transaction - rawTransaction: RawTransaction + transaction: BitcoinTx + rawTransaction: BitcoinRawTx accumulatedTxConfirmations: number latestBlockHeight: number headersChain: string - transactionMerkleBranch: TransactionMerkleBranch + transactionMerkleBranch: BitcoinTxMerkleBranch } - expectedProof: Proof & Transaction + expectedProof: BitcoinSpvProof & BitcoinTx } /** @@ -32,12 +32,12 @@ export const singleInputProofTestData: ProofTestData = { requiredConfirmations: 6, bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "8ee67b585eeb682bf6907ea311282540ee53edf605e0f09757226a4dc3e72a67" ), outputIndex: 0, @@ -102,12 +102,12 @@ export const singleInputProofTestData: ProofTestData = { }, }, expectedProof: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "8ee67b585eeb682bf6907ea311282540ee53edf605e0f09757226a4dc3e72a67" ), outputIndex: 0, @@ -160,33 +160,33 @@ export const multipleInputsProofTestData: ProofTestData = { bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "5083822ed0b8d0bc661362b778e666cb572ff6d5152193992dd69d3207995753" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "ea4d9e45f8c1b8a187c007f36ba1e9b201e8511182c7083c4edcaf9325b2998f" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c844ff4c1781c884bb5e80392398b81b984d7106367ae16675f132bd1a7f33fd" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f548c00e464764e112826450a00cf005ca771a6108a629b559b6c60a519e4378" ), outputIndex: 0, @@ -269,33 +269,33 @@ export const multipleInputsProofTestData: ProofTestData = { }, }, expectedProof: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "5083822ed0b8d0bc661362b778e666cb572ff6d5152193992dd69d3207995753" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "ea4d9e45f8c1b8a187c007f36ba1e9b201e8511182c7083c4edcaf9325b2998f" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c844ff4c1781c884bb5e80392398b81b984d7106367ae16675f132bd1a7f33fd" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), outputIndex: 0, scriptSig: Hex.from(""), }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f548c00e464764e112826450a00cf005ca771a6108a629b559b6c60a519e4378" ), outputIndex: 0, @@ -343,11 +343,11 @@ export const multipleInputsProofTestData: ProofTestData = { export interface TransactionProofData { requiredConfirmations: number bitcoinChainData: { - transaction: Transaction + transaction: BitcoinTx accumulatedTxConfirmations: number latestBlockHeight: number headersChain: string - transactionMerkleBranch: TransactionMerkleBranch + transactionMerkleBranch: BitcoinTxMerkleBranch previousDifficulty: BigNumber currentDifficulty: BigNumber } @@ -362,12 +362,12 @@ export const transactionConfirmationsInOneEpochData: TransactionProofData = { requiredConfirmations: 6, bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "713525ee9d9ab23433cd6ad470566ba1f47cac2d7f119cc50119128a84d718aa" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "91b83d443f32d5a1e87a200eac5d3501af0f156bef3a59d5e8805b4679c4a2a5" ), outputIndex: 3, @@ -460,12 +460,12 @@ export const transactionConfirmationsInTwoEpochsData: TransactionProofData = { requiredConfirmations: 6, bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "e073636400e132b8c1082133ab2b48866919153998f4f04877b580e9932d5a17" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f160a6565d07fd2e8f1d0aaaff538f3150b7f9d2bc64f191076f45c92725b990" ), outputIndex: 0, @@ -535,12 +535,12 @@ export const testnetTransactionData: TransactionProofData = { requiredConfirmations: 6, bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "b78636ae08e6c17261a9f3134109c13c2eb69f6df52e591cc0e0780f5ebf6472" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "b230eb52608287da6320fa0926b3ada60f8979fa662d878494d11909d9841aba" ), outputIndex: 1, diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index 3ba8b0b57..8cefbc42d 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -1,17 +1,17 @@ import { BigNumber, BytesLike } from "ethers" import { - DecomposedRawTransaction, - Proof, - Transaction, - RawTransaction, - UnspentTransactionOutput, - TransactionMerkleBranch, - TransactionHash, - createOutputScriptFromAddress, + BitcoinRawTxVectors, + BitcoinSpvProof, + BitcoinTx, + BitcoinRawTx, + BitcoinUtxo, + BitcoinTxMerkleBranch, + BitcoinTxHash, + BitcoinAddressConverter, } from "../../src/lib/bitcoin" import { RedemptionRequest, WalletState } from "../../src/lib/contracts" import { Address } from "../../src/lib/ethereum" -import { BitcoinTransaction, Hex } from "../../src" +import { Hex } from "../../src" /** * Private key (testnet) of the wallet. @@ -40,15 +40,15 @@ export const p2wpkhWalletAddress = "tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3" * Represents a set of data used for given sweep scenario. */ export interface RedemptionTestData { - mainUtxo: UnspentTransactionOutput & RawTransaction + mainUtxo: BitcoinUtxo & BitcoinRawTx pendingRedemptions: { redemptionKey: BytesLike pendingRedemption: RedemptionRequest }[] witness: boolean expectedRedemption: { - transactionHash: TransactionHash - transaction: RawTransaction + transactionHash: BitcoinTxHash + transaction: BitcoinRawTx } } @@ -59,7 +59,7 @@ export interface RedemptionTestData { */ export const singleP2PKHRedemptionWithWitnessChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "523e4bfb71804e5ed3b76c8933d733339563e560311c1bf835934ee7aae5db20" ), outputIndex: 1, @@ -91,7 +91,7 @@ export const singleP2PKHRedemptionWithWitnessChange: RedemptionTestData = { ], witness: true, expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c437f1117db977682334b53a71fbe63a42aab42f6e0976c35b69977f86308c20" ), transaction: { @@ -114,7 +114,7 @@ export const singleP2PKHRedemptionWithWitnessChange: RedemptionTestData = { */ export const singleP2WPKHRedemptionWithWitnessChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "c437f1117db977682334b53a71fbe63a42aab42f6e0976c35b69977f86308c20" ), outputIndex: 1, @@ -145,7 +145,7 @@ export const singleP2WPKHRedemptionWithWitnessChange: RedemptionTestData = { ], witness: true, expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "925e61dc31396e7f2cbcc8bc9b4009b4f24ba679257762df078b7e9b875ea110" ), transaction: { @@ -168,7 +168,7 @@ export const singleP2WPKHRedemptionWithWitnessChange: RedemptionTestData = { */ export const singleP2SHRedemptionWithWitnessChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "925e61dc31396e7f2cbcc8bc9b4009b4f24ba679257762df078b7e9b875ea110" ), outputIndex: 1, @@ -199,7 +199,7 @@ export const singleP2SHRedemptionWithWitnessChange: RedemptionTestData = { ], witness: true, expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "ef25c9c8f4df673def035c0c1880278c90030b3c94a56668109001a591c2c521" ), transaction: { @@ -222,7 +222,7 @@ export const singleP2SHRedemptionWithWitnessChange: RedemptionTestData = { */ export const singleP2WSHRedemptionWithWitnessChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "ef25c9c8f4df673def035c0c1880278c90030b3c94a56668109001a591c2c521" ), outputIndex: 1, @@ -254,7 +254,7 @@ export const singleP2WSHRedemptionWithWitnessChange: RedemptionTestData = { ], witness: true, expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" ), transaction: { @@ -277,7 +277,7 @@ export const singleP2WSHRedemptionWithWitnessChange: RedemptionTestData = { */ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" ), outputIndex: 1, @@ -349,7 +349,7 @@ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { ], witness: true, expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f70ff89fd2b6226183e4b8143cc5f0f457f05dd1dca0c6151ab66f4523d972b7" ), transaction: { @@ -375,7 +375,7 @@ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { */ export const multipleRedemptionsWithoutChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "7dd38b48cb626580d317871c5b716eaf4a952ceb67ba3aa4ca76e3dc7cdcc65b" ), outputIndex: 1, @@ -420,7 +420,7 @@ export const multipleRedemptionsWithoutChange: RedemptionTestData = { ], witness: true, expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "afcdf8f91273b73abc40018873978c22bbb7c3d8d669ef2faffa0c4b0898c8eb" ), transaction: { @@ -443,7 +443,7 @@ export const multipleRedemptionsWithoutChange: RedemptionTestData = { */ export const singleP2SHRedemptionWithNonWitnessChange: RedemptionTestData = { mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f70ff89fd2b6226183e4b8143cc5f0f457f05dd1dca0c6151ab66f4523d972b7" ), outputIndex: 4, @@ -477,7 +477,7 @@ export const singleP2SHRedemptionWithNonWitnessChange: RedemptionTestData = { ], witness: false, // False will result in a P2PKH output expectedRedemption: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "0fec22d0fecd6607a0429210d04e9465681507d514f3edf0f07def96eda0f89d" ), transaction: { @@ -498,17 +498,17 @@ export const singleP2SHRedemptionWithNonWitnessChange: RedemptionTestData = { */ export interface RedemptionProofTestData { bitcoinChainData: { - transaction: Transaction - rawTransaction: RawTransaction + transaction: BitcoinTx + rawTransaction: BitcoinRawTx accumulatedTxConfirmations: number latestBlockHeight: number headersChain: string - transactionMerkleBranch: TransactionMerkleBranch + transactionMerkleBranch: BitcoinTxMerkleBranch } expectedRedemptionProof: { - redemptionTx: DecomposedRawTransaction - redemptionProof: Proof - mainUtxo: UnspentTransactionOutput + redemptionTx: BitcoinRawTxVectors + redemptionProof: BitcoinSpvProof + mainUtxo: BitcoinUtxo walletPublicKey: string } } @@ -520,12 +520,12 @@ export interface RedemptionProofTestData { export const redemptionProof: RedemptionProofTestData = { bitcoinChainData: { transaction: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f70ff89fd2b6226183e4b8143cc5f0f457f05dd1dca0c6151ab66f4523d972b7" ), inputs: [ { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" ), outputIndex: 1, @@ -657,7 +657,7 @@ export const redemptionProof: RedemptionProofTestData = { "69eab449fb51823d58835a4aed9a5e62341f5c192fd94baa", }, mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" ), outputIndex: 1, @@ -674,8 +674,8 @@ interface FindWalletForRedemptionWalletData { mainUtxoHash: Hex walletPublicKey: Hex btcAddress: string - mainUtxo: UnspentTransactionOutput - transactions: BitcoinTransaction[] + mainUtxo: BitcoinUtxo + transactions: BitcoinTx[] pendingRedemptionsValue: BigNumber } event: { @@ -721,7 +721,7 @@ export const findWalletForRedemptionData: { { outputIndex: 0, value: BigNumber.from("791613461"), - scriptPubKey: createOutputScriptFromAddress( + scriptPubKey: BitcoinAddressConverter.addressToOutputScript( "tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja" ), }, @@ -846,7 +846,7 @@ export const findWalletForRedemptionData: { { outputIndex: 0, value: BigNumber.from("3370000"), // 0.0337 BTC - scriptPubKey: createOutputScriptFromAddress( + scriptPubKey: BitcoinAddressConverter.addressToOutputScript( "tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh" ), }, diff --git a/typescript/test/deposit-refund.test.ts b/typescript/test/deposit-refund.test.ts index a6e30ccf3..bafc68736 100644 --- a/typescript/test/deposit-refund.test.ts +++ b/typescript/test/deposit-refund.test.ts @@ -5,7 +5,7 @@ import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) import { expect } from "chai" import { submitDepositRefundTransaction } from "../src/deposit-refund" -import { TransactionHash, RawTransaction } from "./lib/bitcoin" +import { BitcoinTxHash, BitcoinRawTx } from "./lib/bitcoin" import { refunderPrivateKey, depositRefundOfWitnessDepositAndWitnessRefunderAddress, @@ -25,7 +25,7 @@ describe("Refund", () => { context("when the refund transaction is requested to be witness", () => { context("when the refunded deposit was witness", () => { - let transactionHash: TransactionHash + let transactionHash: BitcoinTxHash beforeEach(async () => { const utxo = @@ -37,7 +37,7 @@ describe("Refund", () => { const refunderPrivateKey = "cTWhf1nXc7aW8BN2qLtWcPtcgcWYKfzRXkCJNsuQ86HR8uJBYfMc" - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(utxo.transactionHash.toString(), { transactionHex: utxo.transactionHex, }) @@ -69,7 +69,7 @@ describe("Refund", () => { }) context("when the refunded deposit was non-witness", () => { - let transactionHash: TransactionHash + let transactionHash: BitcoinTxHash beforeEach(async () => { const utxo = @@ -81,7 +81,7 @@ describe("Refund", () => { const refunderAddress = depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.refunderAddress - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(utxo.transactionHash.toString(), { transactionHex: utxo.transactionHex, }) @@ -116,7 +116,7 @@ describe("Refund", () => { context( "when the refund transaction is requested to be non-witness", () => { - let transactionHash: TransactionHash + let transactionHash: BitcoinTxHash beforeEach(async () => { const utxo = @@ -128,7 +128,7 @@ describe("Refund", () => { const refunderAddress = depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.refunderAddress - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(utxo.transactionHash.toString(), { transactionHex: utxo.transactionHex, }) diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts index e7727de85..c23e0835d 100644 --- a/typescript/test/deposit-sweep.test.ts +++ b/typescript/test/deposit-sweep.test.ts @@ -1,9 +1,9 @@ import { BigNumber } from "ethers" import { - RawTransaction, - TransactionHash, - UnspentTransactionOutput, - Transaction, + BitcoinRawTx, + BitcoinTxHash, + BitcoinUtxo, + BitcoinTx, } from "../src/lib/bitcoin" import { testnetDepositScripthashAddress, @@ -46,13 +46,13 @@ describe("Sweep", () => { context("when the new main UTXO is requested to be witness", () => { context("when there is no main UTXO from previous deposit sweep", () => { - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo beforeEach(async () => { // Map transaction hashes for UTXOs to transactions in hexadecimal and // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() + const rawTransactions = new Map() for (const deposit of depositSweepWithNoMainUtxoAndWitnessOutput.deposits) { rawTransactions.set(deposit.utxo.transactionHash.toString(), { transactionHex: deposit.utxo.transactionHex, @@ -60,7 +60,7 @@ describe("Sweep", () => { } bitcoinClient.rawTransactions = rawTransactions - const utxos: UnspentTransactionOutput[] = + const utxos: BitcoinUtxo[] = depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map((data) => { return data.utxo }) @@ -113,13 +113,13 @@ describe("Sweep", () => { context("when there is main UTXO from previous deposit sweep", () => { context("when main UTXO from previous deposit sweep is witness", () => { - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo beforeEach(async () => { // Map transaction hashes for UTXOs to transactions in hexadecimal and // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() + const rawTransactions = new Map() for (const deposit of depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits) { rawTransactions.set(deposit.utxo.transactionHash.toString(), { transactionHex: deposit.utxo.transactionHex, @@ -134,7 +134,7 @@ describe("Sweep", () => { ) bitcoinClient.rawTransactions = rawTransactions - const utxos: UnspentTransactionOutput[] = + const utxos: BitcoinUtxo[] = depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( (deposit) => { return deposit.utxo @@ -197,13 +197,13 @@ describe("Sweep", () => { context( "when main UTXO from previous deposit sweep is non-witness", () => { - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo beforeEach(async () => { // Map transaction hashes for UTXOs to transactions in hexadecimal and // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() + const rawTransactions = new Map() for (const deposit of depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits) { rawTransactions.set(deposit.utxo.transactionHash.toString(), { transactionHex: deposit.utxo.transactionHex, @@ -219,7 +219,7 @@ describe("Sweep", () => { ) bitcoinClient.rawTransactions = rawTransactions - const utxos: UnspentTransactionOutput[] = + const utxos: BitcoinUtxo[] = depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( (deposit) => { return deposit.utxo @@ -286,13 +286,13 @@ describe("Sweep", () => { // The only difference between deposit sweep transactions with witness and // non-witness output is the output type itself. // Therefore only one test case was added for non-witness transactions. - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo beforeEach(async () => { // Map transaction hashes for UTXOs to transactions in hexadecimal and // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() + const rawTransactions = new Map() for (const deposit of depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits) { rawTransactions.set(deposit.utxo.transactionHash.toString(), { transactionHex: deposit.utxo.transactionHex, @@ -356,9 +356,9 @@ describe("Sweep", () => { describe("assembleDepositSweepTransaction", () => { context("when the new main UTXO is requested to be witness", () => { context("when there is no main UTXO from previous deposit sweep", () => { - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx const utxosWithRaw = depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map((data) => { @@ -477,9 +477,9 @@ describe("Sweep", () => { context("when there is main UTXO from previous deposit sweep", () => { context("when main UTXO prom previous deposit sweep is witness", () => { - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx const utxosWithRaw = depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( @@ -627,9 +627,9 @@ describe("Sweep", () => { context( "when main UTXO from previous deposit sweep is non-witness", () => { - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx const utxosWithRaw = depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( @@ -765,9 +765,9 @@ describe("Sweep", () => { // The only difference between deposit sweep transactions with witness and // non-witness output is the output type itself. // Therefore only one test case was added for non-witness transactions. - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx const utxosWithRaw = depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( @@ -952,7 +952,7 @@ describe("Sweep", () => { // The UTXO below does not belong to the wallet const mainUtxoWithRaw = { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883" ), outputIndex: 1, @@ -1010,7 +1010,7 @@ describe("Sweep", () => { context("when the type of UTXO is unsupported", () => { // Use coinbase transaction of some block const utxoWithRaw = { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "025de155e6f2ffbbf4851493e0d28dad54020db221a3f38bf63c1f65e3d3595b" ), outputIndex: 0, @@ -1050,14 +1050,14 @@ describe("Sweep", () => { const transactionHash = depositSweepProof.bitcoinChainData.transaction.transactionHash - const transactions = new Map() + const transactions = new Map() transactions.set( transactionHash.toString(), depositSweepProof.bitcoinChainData.transaction ) bitcoinClient.transactions = transactions - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set( transactionHash.toString(), depositSweepProof.bitcoinChainData.rawTransaction diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index 7e34edd9c..f002c570d 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -8,10 +8,10 @@ import { testnetUTXO, } from "./data/deposit" import { - decomposeRawTransaction, - RawTransaction, - TransactionHash, - UnspentTransactionOutput, + extractBitcoinRawTxVectors, + BitcoinRawTx, + BitcoinTxHash, + BitcoinUtxo, } from "../src/lib/bitcoin" import { Deposit, @@ -70,7 +70,7 @@ describe("Deposit", () => { // Expected data of created deposit in P2WSH scenarios. const expectedP2WSHDeposit = { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "9eb901fc68f0d9bcaf575f23783b7d30ac5dd8d95f3c83dceaa13dce17de816a" ), @@ -100,7 +100,7 @@ describe("Deposit", () => { // Expected data of created deposit in P2SH scenarios. const expectedP2SHDeposit = { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f21a9922c0c136c6d288cf1258b732d0f84a7d50d14a01d7d81cb6cd810f3517" ), @@ -232,20 +232,20 @@ describe("Deposit", () => { // Tie used testnetAddress with testnetUTXO to use it during deposit // creation. - const utxos = new Map() + const utxos = new Map() utxos.set(testnetAddress, [testnetUTXO]) bitcoinClient.unspentTransactionOutputs = utxos // Tie testnetTransaction to testnetUTXO. This is needed since // submitDepositTransaction attach transaction data to each UTXO. - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(testnetTransactionHash.toString(), testnetTransaction) bitcoinClient.rawTransactions = rawTransactions }) context("when witness option is true", () => { - let transactionHash: TransactionHash - let depositUtxo: UnspentTransactionOutput + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo beforeEach(async () => { ;({ transactionHash, depositUtxo } = await submitDepositTransaction( @@ -281,8 +281,8 @@ describe("Deposit", () => { }) context("when witness option is false", () => { - let transactionHash: TransactionHash - let depositUtxo: UnspentTransactionOutput + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo beforeEach(async () => { ;({ transactionHash, depositUtxo } = await submitDepositTransaction( @@ -320,9 +320,9 @@ describe("Deposit", () => { describe("assembleDepositTransaction", () => { context("when witness option is true", () => { - let transactionHash: TransactionHash - let depositUtxo: UnspentTransactionOutput - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo + let transaction: BitcoinRawTx beforeEach(async () => { ;({ @@ -417,9 +417,9 @@ describe("Deposit", () => { }) context("when witness option is false", () => { - let transactionHash: TransactionHash - let depositUtxo: UnspentTransactionOutput - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo + let transaction: BitcoinRawTx beforeEach(async () => { ;({ @@ -693,8 +693,8 @@ describe("Deposit", () => { }) describe("revealDeposit", () => { - let transaction: RawTransaction - let depositUtxo: UnspentTransactionOutput + let transaction: BitcoinRawTx + let depositUtxo: BitcoinUtxo let bitcoinClient: MockBitcoinClient let bridge: MockBridge @@ -713,7 +713,7 @@ describe("Deposit", () => { // Initialize the mock Bitcoin client to return the raw transaction // data for the given deposit UTXO. bitcoinClient = new MockBitcoinClient() - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(depositUtxo.transactionHash.toString(), transaction) bitcoinClient.rawTransactions = rawTransactions @@ -728,7 +728,7 @@ describe("Deposit", () => { const revealDepositLogEntry = bridge.revealDepositLog[0] expect(revealDepositLogEntry.depositTx).to.be.eql( - decomposeRawTransaction(transaction) + extractBitcoinRawTxVectors(transaction) ) expect(revealDepositLogEntry.depositOutputIndex).to.be.equal(0) expect(revealDepositLogEntry.deposit).to.be.eql(deposit) @@ -736,7 +736,7 @@ describe("Deposit", () => { }) describe("getRevealedDeposit", () => { - let depositUtxo: UnspentTransactionOutput + let depositUtxo: BitcoinUtxo let revealedDeposit: RevealedDeposit let bridge: MockBridge diff --git a/typescript/test/ethereum.test.ts b/typescript/test/ethereum.test.ts index ae98d3f9a..363202e9b 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/ethereum.test.ts @@ -10,7 +10,7 @@ import { abi as TBTCTokenABI } from "@keep-network/tbtc-v2/artifacts/TBTC.json" import { abi as WalletRegistryABI } from "@keep-network/ecdsa/artifacts/WalletRegistry.json" import { MockProvider } from "@ethereum-waffle/provider" import { waffleChai } from "@ethereum-waffle/chai" -import { TransactionHash, computeHash160 } from "../src/lib/bitcoin" +import { BitcoinTxHash, BitcoinHashUtils } from "../src/lib/bitcoin" import { Hex } from "../src/lib/utils" chai.use(waffleChai) @@ -182,7 +182,7 @@ describe("Ethereum", () => { bitcoinHeaders: "66666666", }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f8eaf242a55ea15e602f9f990e33f67f99dfbe25d1802bbde63cc1caabf99668" ), outputIndex: 8, @@ -235,7 +235,7 @@ describe("Ethereum", () => { await bridgeHandle.requestRedemption( "03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9", { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f8eaf242a55ea15e602f9f990e33f67f99dfbe25d1802bbde63cc1caabf99668" ), outputIndex: 8, @@ -278,7 +278,7 @@ describe("Ethereum", () => { bitcoinHeaders: "66666666", }, { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f8eaf242a55ea15e602f9f990e33f67f99dfbe25d1802bbde63cc1caabf99668" ), outputIndex: 8, @@ -336,7 +336,7 @@ describe("Ethereum", () => { it("should return the revealed deposit", async () => { expect( await bridgeHandle.deposits( - TransactionHash.from( + BitcoinTxHash.from( "c1082c460527079a84e39ec6481666db72e5a22e473a78db03b996d26fd1dc83" ), 0 @@ -375,7 +375,7 @@ describe("Ethereum", () => { it("should return the revealed deposit", async () => { expect( await bridgeHandle.deposits( - TransactionHash.from( + BitcoinTxHash.from( "c1082c460527079a84e39ec6481666db72e5a22e473a78db03b996d26fd1dc83" ), 0 @@ -496,7 +496,7 @@ describe("Ethereum", () => { walletPublicKey: "03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9", mainUtxo: { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "f8eaf242a55ea15e602f9f990e33f67f99dfbe25d1802bbde63cc1caabf99668" ), outputIndex: 8, @@ -537,7 +537,9 @@ describe("Ethereum", () => { ["address", "bytes20", "bytes32", "uint32", "uint64", "bytes"], [ redeemer.identifierHex, - Hex.from(computeHash160(walletPublicKey)).toPrefixedString(), + Hex.from( + BitcoinHashUtils.computeHash160(walletPublicKey) + ).toPrefixedString(), mainUtxo.transactionHash.reverse().toPrefixedString(), mainUtxo.outputIndex, mainUtxo.value, diff --git a/typescript/test/proof.test.ts b/typescript/test/proof.test.ts index 37c47ea8d..9f499ac1e 100644 --- a/typescript/test/proof.test.ts +++ b/typescript/test/proof.test.ts @@ -1,12 +1,11 @@ import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { - serializeBlockHeader, - Transaction, - BlockHeader, - Proof, - assembleTransactionProof, - validateTransactionProof, - splitBlockHeadersChain, + BitcoinHeaderSerializer, + BitcoinTx, + BitcoinHeader, + BitcoinSpvProof, + assembleBitcoinSpvProof, + validateBitcoinSpvProof, } from "../src/lib/bitcoin" import { Hex } from "../src/lib/utils" import { @@ -32,7 +31,7 @@ describe("Proof", () => { }) context("when the transaction has one input", () => { - let proof: Transaction & Proof + let proof: BitcoinTx & BitcoinSpvProof beforeEach(async () => { proof = await runProofScenario(singleInputProofTestData) @@ -52,7 +51,7 @@ describe("Proof", () => { }) context("when the transaction has multiple inputs", () => { - let proof: Transaction & Proof + let proof: BitcoinTx & BitcoinSpvProof beforeEach(async () => { proof = await runProofScenario(multipleInputsProofTestData) @@ -90,8 +89,8 @@ describe("Proof", () => { async function runProofScenario( data: ProofTestData - ): Promise { - const transactions = new Map() + ): Promise { + const transactions = new Map() const transactionHash = data.bitcoinChainData.transaction.transactionHash transactions.set( transactionHash.toString(), @@ -109,7 +108,7 @@ describe("Proof", () => { ) bitcoinClient.confirmations = confirmations - const proof = await assembleTransactionProof( + const proof = await assembleBitcoinSpvProof( transactionHash, data.requiredConfirmations, bitcoinClient @@ -283,14 +282,16 @@ describe("Proof", () => { it("should throw", async () => { // Corrupt data by modifying previous block header hash of one of the // headers. - const headers: BlockHeader[] = splitBlockHeadersChain( - transactionConfirmationsInOneEpochData.bitcoinChainData.headersChain - ) + const headers: BitcoinHeader[] = + BitcoinHeaderSerializer.deserializeHeadersChain( + transactionConfirmationsInOneEpochData.bitcoinChainData + .headersChain + ) headers[headers.length - 1].previousBlockHeaderHash = Hex.from( "ff".repeat(32) ) const corruptedHeadersChain: string = headers - .map(serializeBlockHeader) + .map(BitcoinHeaderSerializer.serializeHeader) .join("") const corruptedProofData: TransactionProofData = { @@ -311,12 +312,14 @@ describe("Proof", () => { it("should throw", async () => { // Corrupt data by modifying the nonce of one of the headers, so that // the resulting hash will be above the required difficulty target. - const headers: BlockHeader[] = splitBlockHeadersChain( - transactionConfirmationsInOneEpochData.bitcoinChainData.headersChain - ) + const headers: BitcoinHeader[] = + BitcoinHeaderSerializer.deserializeHeadersChain( + transactionConfirmationsInOneEpochData.bitcoinChainData + .headersChain + ) headers[headers.length - 1].nonce++ const corruptedHeadersChain: string = headers - .map(serializeBlockHeader) + .map(BitcoinHeaderSerializer.serializeHeader) .join("") const corruptedProofData: TransactionProofData = { @@ -361,7 +364,7 @@ describe("Proof", () => { }) async function runProofValidationScenario(data: TransactionProofData) { - const transactions = new Map() + const transactions = new Map() const transactionHash = data.bitcoinChainData.transaction.transactionHash transactions.set( transactionHash.toString(), @@ -379,7 +382,7 @@ describe("Proof", () => { ) bitcoinClient.confirmations = confirmations - await validateTransactionProof( + await validateBitcoinSpvProof( data.bitcoinChainData.transaction.transactionHash, data.requiredConfirmations, data.bitcoinChainData.previousDifficulty, diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index ee59f3b48..ef90d3cae 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -1,9 +1,9 @@ import { BitcoinNetwork, - Transaction, - RawTransaction, - TransactionHash, - UnspentTransactionOutput, + BitcoinTx, + BitcoinRawTx, + BitcoinTxHash, + BitcoinUtxo, } from "../src/lib/bitcoin" import bcoin from "bcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" @@ -38,7 +38,6 @@ import chaiAsPromised from "chai-as-promised" import { expect } from "chai" import { BigNumberish, BigNumber } from "ethers" import { MockTBTCToken } from "./utils/mock-tbtc-token" -import { BitcoinTransaction } from "../src" chai.use(chaiAsPromised) @@ -99,8 +98,8 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = @@ -144,8 +143,8 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2WPKHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = @@ -189,8 +188,8 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2SHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = @@ -234,8 +233,8 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2WSHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = @@ -278,8 +277,8 @@ describe("Redemption", () => { const data: RedemptionTestData = multipleRedemptionsWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = @@ -323,8 +322,8 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2SHRedemptionWithNonWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = @@ -367,8 +366,8 @@ describe("Redemption", () => { // will not contain the change output. const data: RedemptionTestData = multipleRedemptionsWithoutChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined beforeEach(async () => { ;({ transactionHash, newMainUtxo } = await runRedemptionScenario( @@ -405,7 +404,7 @@ describe("Redemption", () => { const data: RedemptionTestData = multipleRedemptionsWithWitnessChange beforeEach(async () => { - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(data.mainUtxo.transactionHash.toString(), { transactionHex: data.mainUtxo.transactionHex, }) @@ -450,7 +449,7 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2WPKHRedemptionWithWitnessChange beforeEach(async () => { - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(data.mainUtxo.transactionHash.toString(), { transactionHex: data.mainUtxo.transactionHex, }) @@ -483,9 +482,9 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -596,9 +595,9 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2WPKHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -708,9 +707,9 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2SHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -820,9 +819,9 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2WSHRedemptionWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -931,9 +930,9 @@ describe("Redemption", () => { const data: RedemptionTestData = multipleRedemptionsWithWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -1088,9 +1087,9 @@ describe("Redemption", () => { const data: RedemptionTestData = singleP2SHRedemptionWithNonWitnessChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -1194,9 +1193,9 @@ describe("Redemption", () => { context("when there is no change UTXO created", () => { const data: RedemptionTestData = multipleRedemptionsWithoutChange - let transactionHash: TransactionHash - let newMainUtxo: UnspentTransactionOutput | undefined - let transaction: RawTransaction + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { const redemptionRequests = data.pendingRedemptions.map( @@ -1309,7 +1308,7 @@ describe("Redemption", () => { describe("submitRedemptionProof", () => { const mainUtxo = { - transactionHash: TransactionHash.from( + transactionHash: BitcoinTxHash.from( "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" ), outputIndex: 1, @@ -1328,14 +1327,14 @@ describe("Redemption", () => { const transactionHash = redemptionProof.bitcoinChainData.transaction.transactionHash - const transactions = new Map() + const transactions = new Map() transactions.set( transactionHash.toString(), redemptionProof.bitcoinChainData.transaction ) bitcoinClient.transactions = transactions - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set( transactionHash.toString(), redemptionProof.bitcoinChainData.rawTransaction @@ -1490,10 +1489,7 @@ describe("Redemption", () => { (wallet) => wallet.event ) - const walletsTransactionHistory = new Map< - string, - BitcoinTransaction[] - >() + const walletsTransactionHistory = new Map() walletsOrder.forEach((wallet) => { const { @@ -1740,10 +1736,10 @@ async function runRedemptionScenario( bridge: MockBridge, data: RedemptionTestData ): Promise<{ - transactionHash: TransactionHash - newMainUtxo?: UnspentTransactionOutput + transactionHash: BitcoinTxHash + newMainUtxo?: BitcoinUtxo }> { - const rawTransactions = new Map() + const rawTransactions = new Map() rawTransactions.set(data.mainUtxo.transactionHash.toString(), { transactionHex: data.mainUtxo.transactionHex, }) diff --git a/typescript/test/utils/mock-bitcoin-client.ts b/typescript/test/utils/mock-bitcoin-client.ts index fdb0e17fb..96b01b249 100644 --- a/typescript/test/utils/mock-bitcoin-client.ts +++ b/typescript/test/utils/mock-bitcoin-client.ts @@ -1,45 +1,40 @@ import { BitcoinNetwork, - Client, - UnspentTransactionOutput, - TransactionMerkleBranch, - RawTransaction, - Transaction, - TransactionHash, + BitcoinClient, + BitcoinUtxo, + BitcoinTxMerkleBranch, + BitcoinRawTx, + BitcoinTx, + BitcoinTxHash, } from "../../src/lib/bitcoin" /** * Mock Bitcoin client used for test purposes. */ -export class MockBitcoinClient implements Client { - private _unspentTransactionOutputs = new Map< - string, - UnspentTransactionOutput[] - >() - private _rawTransactions = new Map() - private _transactions = new Map() +export class MockBitcoinClient implements BitcoinClient { + private _unspentTransactionOutputs = new Map() + private _rawTransactions = new Map() + private _transactions = new Map() private _confirmations = new Map() private _latestHeight = 0 private _headersChain = "" - private _transactionMerkle: TransactionMerkleBranch = { + private _transactionMerkle: BitcoinTxMerkleBranch = { blockHeight: 0, merkle: [], position: 0, } - private _broadcastLog: RawTransaction[] = [] - private _transactionHistory = new Map() + private _broadcastLog: BitcoinRawTx[] = [] + private _transactionHistory = new Map() - set unspentTransactionOutputs( - value: Map - ) { + set unspentTransactionOutputs(value: Map) { this._unspentTransactionOutputs = value } - set rawTransactions(value: Map) { + set rawTransactions(value: Map) { this._rawTransactions = value } - set transactions(value: Map) { + set transactions(value: Map) { this._transactions = value } @@ -55,15 +50,15 @@ export class MockBitcoinClient implements Client { this._headersChain = value } - set transactionMerkle(value: TransactionMerkleBranch) { + set transactionMerkle(value: BitcoinTxMerkleBranch) { this._transactionMerkle = value } - set transactionHistory(value: Map) { + set transactionHistory(value: Map) { this._transactionHistory = value } - get broadcastLog(): RawTransaction[] { + get broadcastLog(): BitcoinRawTx[] { return this._broadcastLog } @@ -73,24 +68,15 @@ export class MockBitcoinClient implements Client { }) } - findAllUnspentTransactionOutputs( - address: string - ): Promise { - return new Promise((resolve, _) => { - resolve( - this._unspentTransactionOutputs.get( - address - ) as UnspentTransactionOutput[] - ) + findAllUnspentTransactionOutputs(address: string): Promise { + return new Promise((resolve, _) => { + resolve(this._unspentTransactionOutputs.get(address) as BitcoinUtxo[]) }) } - getTransactionHistory( - address: string, - limit?: number - ): Promise { - return new Promise((resolve, _) => { - let transactions = this._transactionHistory.get(address) as Transaction[] + getTransactionHistory(address: string, limit?: number): Promise { + return new Promise((resolve, _) => { + let transactions = this._transactionHistory.get(address) as BitcoinTx[] if ( typeof limit !== "undefined" && @@ -104,23 +90,21 @@ export class MockBitcoinClient implements Client { }) } - getTransaction(transactionHash: TransactionHash): Promise { - return new Promise((resolve, _) => { - resolve(this._transactions.get(transactionHash.toString()) as Transaction) + getTransaction(transactionHash: BitcoinTxHash): Promise { + return new Promise((resolve, _) => { + resolve(this._transactions.get(transactionHash.toString()) as BitcoinTx) }) } - getRawTransaction(transactionHash: TransactionHash): Promise { - return new Promise((resolve, _) => { + getRawTransaction(transactionHash: BitcoinTxHash): Promise { + return new Promise((resolve, _) => { resolve( - this._rawTransactions.get(transactionHash.toString()) as RawTransaction + this._rawTransactions.get(transactionHash.toString()) as BitcoinRawTx ) }) } - getTransactionConfirmations( - transactionHash: TransactionHash - ): Promise { + getTransactionConfirmations(transactionHash: BitcoinTxHash): Promise { return new Promise((resolve, _) => { resolve(this._confirmations.get(transactionHash.toString()) as number) }) @@ -139,15 +123,15 @@ export class MockBitcoinClient implements Client { } getTransactionMerkle( - transactionHash: TransactionHash, + transactionHash: BitcoinTxHash, blockHeight: number - ): Promise { - return new Promise((resolve, _) => { + ): Promise { + return new Promise((resolve, _) => { resolve(this._transactionMerkle) }) } - broadcast(transaction: RawTransaction): Promise { + broadcast(transaction: BitcoinRawTx): Promise { this._broadcastLog.push(transaction) return new Promise((resolve, _) => { resolve() diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index bdbca6683..aabdc0c92 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -12,11 +12,11 @@ import { RevealedDeposit, } from "../../src/lib/contracts" import { - DecomposedRawTransaction, - Proof, - UnspentTransactionOutput, - computeHash160, - TransactionHash, + BitcoinRawTxVectors, + BitcoinSpvProof, + BitcoinUtxo, + BitcoinHashUtils, + BitcoinTxHash, } from "../../src/lib/bitcoin" import { BigNumberish, BigNumber, utils, constants } from "ethers" import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" @@ -24,28 +24,28 @@ import { Address } from "../../src/lib/ethereum" import { Hex } from "../../src/lib/utils" interface DepositSweepProofLogEntry { - sweepTx: DecomposedRawTransaction - sweepProof: Proof - mainUtxo: UnspentTransactionOutput + sweepTx: BitcoinRawTxVectors + sweepProof: BitcoinSpvProof + mainUtxo: BitcoinUtxo } interface RevealDepositLogEntry { - depositTx: DecomposedRawTransaction + depositTx: BitcoinRawTxVectors depositOutputIndex: number deposit: Deposit } interface RequestRedemptionLogEntry { walletPublicKey: string - mainUtxo: UnspentTransactionOutput + mainUtxo: BitcoinUtxo redeemerOutputScript: string amount: BigNumber } interface RedemptionProofLogEntry { - redemptionTx: DecomposedRawTransaction - redemptionProof: Proof - mainUtxo: UnspentTransactionOutput + redemptionTx: BitcoinRawTxVectors + redemptionProof: BitcoinSpvProof + mainUtxo: BitcoinUtxo walletPublicKey: string } @@ -155,9 +155,9 @@ export class MockBridge implements Bridge { } submitDepositSweepProof( - sweepTx: DecomposedRawTransaction, - sweepProof: Proof, - mainUtxo: UnspentTransactionOutput, + sweepTx: BitcoinRawTxVectors, + sweepProof: BitcoinSpvProof, + mainUtxo: BitcoinUtxo, vault?: Identifier ): Promise { this._depositSweepProofLog.push({ sweepTx, sweepProof, mainUtxo }) @@ -167,7 +167,7 @@ export class MockBridge implements Bridge { } revealDeposit( - depositTx: DecomposedRawTransaction, + depositTx: BitcoinRawTxVectors, depositOutputIndex: number, deposit: Deposit ): Promise { @@ -181,7 +181,7 @@ export class MockBridge implements Bridge { } deposits( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { return new Promise((resolve, _) => { @@ -206,7 +206,7 @@ export class MockBridge implements Bridge { } static buildDepositKey( - depositTxHash: TransactionHash, + depositTxHash: BitcoinTxHash, depositOutputIndex: number ): string { const prefixedReversedDepositTxHash = depositTxHash @@ -220,9 +220,9 @@ export class MockBridge implements Bridge { } submitRedemptionProof( - redemptionTx: DecomposedRawTransaction, - redemptionProof: Proof, - mainUtxo: UnspentTransactionOutput, + redemptionTx: BitcoinRawTxVectors, + redemptionProof: BitcoinSpvProof, + mainUtxo: BitcoinUtxo, walletPublicKey: string ): Promise { this._redemptionProofLog.push({ @@ -238,7 +238,7 @@ export class MockBridge implements Bridge { requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber ) { @@ -295,7 +295,7 @@ export class MockBridge implements Bridge { redemptionsMap: Map ): RedemptionRequest { const redemptionKey = MockBridge.buildRedemptionKey( - computeHash160(walletPublicKey), + BitcoinHashUtils.computeHash160(walletPublicKey), redeemerOutputScript ) @@ -359,7 +359,7 @@ export class MockBridge implements Bridge { return wallet! } - buildUtxoHash(utxo: UnspentTransactionOutput): Hex { + buildUtxoHash(utxo: BitcoinUtxo): Hex { return Hex.from( utils.solidityKeccak256( ["bytes32", "uint32", "uint64"], diff --git a/typescript/test/utils/mock-tbtc-token.ts b/typescript/test/utils/mock-tbtc-token.ts index 588615404..29ba32497 100644 --- a/typescript/test/utils/mock-tbtc-token.ts +++ b/typescript/test/utils/mock-tbtc-token.ts @@ -1,11 +1,11 @@ import { TBTCToken } from "../../src/lib/contracts" import { Hex } from "../../src/lib/utils" import { BigNumber } from "ethers" -import { UnspentTransactionOutput } from "../../src/lib/bitcoin" +import { BitcoinUtxo } from "../../src/lib/bitcoin" interface RequestRedemptionLog { walletPublicKey: string - mainUtxo: UnspentTransactionOutput + mainUtxo: BitcoinUtxo redeemerOutputScript: string amount: BigNumber } @@ -23,7 +23,7 @@ export class MockTBTCToken implements TBTCToken { async requestRedemption( walletPublicKey: string, - mainUtxo: UnspentTransactionOutput, + mainUtxo: BitcoinUtxo, redeemerOutputScript: string, amount: BigNumber ): Promise { diff --git a/typescript/test/wallet.test.ts b/typescript/test/wallet.test.ts index 5e5f83997..5cbd48fce 100644 --- a/typescript/test/wallet.test.ts +++ b/typescript/test/wallet.test.ts @@ -1,9 +1,13 @@ import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { MockBridge } from "./utils/mock-bridge" -import { BitcoinNetwork, BitcoinTransaction, Hex } from "../src" +import { Hex } from "../src" import { determineWalletMainUtxo } from "../src/wallet" import { expect } from "chai" -import { encodeToBitcoinAddress } from "../src/lib/bitcoin" +import { + BitcoinNetwork, + BitcoinAddressConverter, + BitcoinTx, +} from "../src/lib/bitcoin" import { Wallet } from "../src/lib/contracts" import { BigNumber } from "ethers" @@ -18,7 +22,7 @@ describe("Wallet", () => { const mockTransaction = ( hash: string, outputs: Record // key: locking script, value: amount of locked satoshis - ): BitcoinTransaction => { + ): BitcoinTx => { return { transactionHash: Hex.from(hash), inputs: [], // not relevant in this test scenario @@ -33,7 +37,7 @@ describe("Wallet", () => { } // Create a fake wallet witness transaction history that consists of 6 transactions. - const walletWitnessTransactionHistory: BitcoinTransaction[] = [ + const walletWitnessTransactionHistory: BitcoinTx[] = [ mockTransaction( "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", { @@ -80,7 +84,7 @@ describe("Wallet", () => { ] // Create a fake wallet legacy transaction history that consists of 6 transactions. - const walletLegacyTransactionHistory: BitcoinTransaction[] = [ + const walletLegacyTransactionHistory: BitcoinTx[] = [ mockTransaction( "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267", { @@ -238,22 +242,21 @@ describe("Wallet", () => { beforeEach(async () => { bitcoinNetwork = network - const walletWitnessAddress = encodeToBitcoinAddress( - walletPublicKeyHash.toString(), - true, - bitcoinNetwork - ) - const walletLegacyAddress = encodeToBitcoinAddress( - walletPublicKeyHash.toString(), - false, - bitcoinNetwork - ) + const walletWitnessAddress = + BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + true, + bitcoinNetwork + ) + const walletLegacyAddress = + BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + false, + bitcoinNetwork + ) // Record the fake transaction history for both address types. - const transactionHistory = new Map< - string, - BitcoinTransaction[] - >() + const transactionHistory = new Map() transactionHistory.set( walletWitnessAddress, walletWitnessTransactionHistory From 955788dc3ca394fd49cbbd9d7ffed2aada4655e9 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 26 Sep 2023 11:13:38 +0200 Subject: [PATCH 12/43] Final iteration on `lib/contracts` Here we make a final rework of the `lib/contracts` module. The main work is focused on improving component names. --- typescript/src/deposit-sweep.ts | 4 +-- typescript/src/deposit.ts | 4 +-- typescript/src/lib/contracts/bridge.ts | 26 +++++++-------- typescript/src/lib/contracts/chain-event.ts | 6 ++-- .../src/lib/contracts/chain-identifier.ts | 4 +-- typescript/src/lib/contracts/tbtc-token.ts | 2 +- typescript/src/lib/contracts/tbtc-vault.ts | 32 +++++++++---------- .../src/lib/contracts/wallet-registry.ts | 20 ++++++------ typescript/src/lib/ethereum/address.ts | 2 +- typescript/src/lib/ethereum/bridge.ts | 10 +++--- .../src/lib/ethereum/contract-handle.ts | 4 +-- typescript/src/lib/ethereum/tbtc-vault.ts | 8 ++--- .../src/lib/ethereum/wallet-registry.ts | 8 ++--- typescript/test/utils/mock-bridge.ts | 14 ++++---- 14 files changed, 72 insertions(+), 72 deletions(-) diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 6b20c142f..2961ece02 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -12,7 +12,7 @@ import { BitcoinHashUtils, } from "./lib/bitcoin" import { assembleDepositScript } from "./deposit" -import { Bridge, Identifier, Deposit } from "./lib/contracts" +import { Bridge, ChainIdentifier, Deposit } from "./lib/contracts" /** * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and @@ -403,7 +403,7 @@ export async function submitDepositSweepProof( mainUtxo: BitcoinUtxo, bridge: Bridge, bitcoinClient: BitcoinClient, - vault?: Identifier + vault?: ChainIdentifier ): Promise { const confirmations = await bridge.txProofDifficultyFactor() const proof = await assembleBitcoinSpvProof( diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index 78b2b8fcc..4d4684998 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -12,7 +12,7 @@ import { } from "./lib/bitcoin" import { Bridge, - Identifier, + ChainIdentifier, Deposit, DepositScriptParameters, RevealedDeposit, @@ -298,7 +298,7 @@ export async function revealDeposit( deposit: DepositScriptParameters, bitcoinClient: BitcoinClient, bridge: Bridge, - vault?: Identifier + vault?: ChainIdentifier ): Promise { const depositTx = extractBitcoinRawTxVectors( await bitcoinClient.getRawTransaction(utxo.transactionHash) diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index b92d54911..eaea4f64b 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -6,8 +6,8 @@ import { BitcoinTxHash, } from "../bitcoin" import { Hex } from "../utils" -import { Event, GetEvents } from "./chain-event" -import { Identifier } from "./chain-identifier" +import { ChainEvent, GetChainEvents } from "./chain-event" +import { ChainIdentifier } from "./chain-identifier" import { WalletRegistry } from "./wallet-registry" /** @@ -18,7 +18,7 @@ export interface Bridge { * Get emitted DepositRevealed events. * @see GetEventsFunction */ - getDepositRevealedEvents: GetEvents.Function + getDepositRevealedEvents: GetChainEvents.Function /** * Submits a deposit sweep transaction proof to the on-chain contract. @@ -32,7 +32,7 @@ export interface Bridge { sweepTx: BitcoinRawTxVectors, sweepProof: BitcoinSpvProof, mainUtxo: BitcoinUtxo, - vault?: Identifier + vault?: ChainIdentifier ): Promise /** @@ -49,7 +49,7 @@ export interface Bridge { depositTx: BitcoinRawTxVectors, depositOutputIndex: number, deposit: DepositScriptParameters, - vault?: Identifier + vault?: ChainIdentifier ): Promise // TODO: Update to Hex /** @@ -147,7 +147,7 @@ export interface Bridge { * Get emitted NewWalletRegisteredEvent events. * @see GetEventsFunction */ - getNewWalletRegisteredEvents: GetEvents.Function + getNewWalletRegisteredEvents: GetChainEvents.Function /** * Returns the attached WalletRegistry instance. @@ -173,7 +173,7 @@ export interface Bridge { * Get emitted RedemptionRequested events. * @see GetEventsFunction */ - getRedemptionRequestedEvents: GetEvents.Function + getRedemptionRequestedEvents: GetChainEvents.Function } // TODO: Replace all properties that are expected to be un-prefixed hexadecimal @@ -186,7 +186,7 @@ export interface Deposit { /** * Depositor's chain identifier. */ - depositor: Identifier + depositor: ChainIdentifier /** * Deposit amount in satoshis. @@ -223,7 +223,7 @@ export interface Deposit { /** * Optional identifier of the vault the deposit should be routed in. */ - vault?: Identifier + vault?: ChainIdentifier } /** @@ -270,7 +270,7 @@ export type RevealedDeposit = Pick< export type DepositRevealedEvent = Deposit & { fundingTxHash: BitcoinTxHash fundingOutputIndex: number -} & Event +} & ChainEvent /** * Represents a redemption request. @@ -279,7 +279,7 @@ export interface RedemptionRequest { /** * On-chain identifier of the redeemer. */ - redeemer: Identifier + redeemer: ChainIdentifier /** * The output script the redeemed Bitcoin funds are locked to. It is un-prefixed @@ -326,7 +326,7 @@ export type RedemptionRequestedEvent = Omit< * be an unprefixed hex string (without 0x prefix). */ walletPublicKeyHash: string -} & Event +} & ChainEvent /* eslint-disable no-unused-vars */ export enum WalletState { @@ -439,4 +439,4 @@ export type NewWalletRegisteredEvent = { * hash160 on the compressed public key of the ECDSA Wallet. */ walletPublicKeyHash: Hex -} & Event +} & ChainEvent diff --git a/typescript/src/lib/contracts/chain-event.ts b/typescript/src/lib/contracts/chain-event.ts index 2d58b9ddd..e93a4c411 100644 --- a/typescript/src/lib/contracts/chain-event.ts +++ b/typescript/src/lib/contracts/chain-event.ts @@ -3,7 +3,7 @@ import { ExecutionLoggerFn, Hex } from "../utils" /** * Represents a generic chain event. */ -export interface Event { +export interface ChainEvent { /** * Block number of the event emission. */ @@ -18,7 +18,7 @@ export interface Event { transactionHash: Hex } -export namespace GetEvents { +export namespace GetChainEvents { /** * Represents generic options used for getting events from the chain. */ @@ -50,7 +50,7 @@ export namespace GetEvents { /** * Represents a generic function to get events emitted on the chain. */ - export interface Function { + export interface Function { /** * Get emitted events. * @param options Options for getting events. diff --git a/typescript/src/lib/contracts/chain-identifier.ts b/typescript/src/lib/contracts/chain-identifier.ts index 3591f7bd1..de598d079 100644 --- a/typescript/src/lib/contracts/chain-identifier.ts +++ b/typescript/src/lib/contracts/chain-identifier.ts @@ -1,7 +1,7 @@ /** * Represents a generic chain identifier. */ -export interface Identifier { +export interface ChainIdentifier { /** * Identifier as an un-prefixed hex string. */ @@ -11,5 +11,5 @@ export interface Identifier { * * @param identifier Another identifier */ - equals(identifier: Identifier): boolean + equals(identifier: ChainIdentifier): boolean } diff --git a/typescript/src/lib/contracts/tbtc-token.ts b/typescript/src/lib/contracts/tbtc-token.ts index 9e32a4801..7770510dd 100644 --- a/typescript/src/lib/contracts/tbtc-token.ts +++ b/typescript/src/lib/contracts/tbtc-token.ts @@ -19,7 +19,7 @@ export interface TBTCToken { totalSupply(blockNumber?: number): Promise /** - * Requests redemption in one transacion using the `approveAndCall` function + * Requests redemption in one transaction using the `approveAndCall` function * from the tBTC on-chain token contract. Then the tBTC token contract calls * the `receiveApproval` function from the `TBTCVault` contract which burns * tBTC tokens and requests redemption. diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts index 8a61c5f8e..2869fa4a8 100644 --- a/typescript/src/lib/contracts/tbtc-vault.ts +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -1,7 +1,7 @@ import { BitcoinTxHash } from "../bitcoin" import { Hex } from "../utils" -import { Identifier } from "./chain-identifier" -import { Event, GetEvents } from "./chain-event" +import { ChainIdentifier } from "./chain-identifier" +import { ChainEvent, GetChainEvents } from "./chain-event" import { BigNumber } from "ethers" /** @@ -22,21 +22,21 @@ export interface TBTCVault { * * @returns Array containing identifiers of all currently registered minters. */ - getMinters(): Promise + getMinters(): Promise /** * Checks if given identifier is registered as minter. * * @param identifier Chain identifier to check. */ - isMinter(identifier: Identifier): Promise + isMinter(identifier: ChainIdentifier): Promise /** * Checks if given identifier is registered as guardian. * * @param identifier Chain identifier to check. */ - isGuardian(identifier: Identifier): Promise + isGuardian(identifier: ChainIdentifier): Promise /** * Requests optimistic minting for a deposit in an on-chain contract. @@ -93,19 +93,19 @@ export interface TBTCVault { * Get emitted OptimisticMintingRequested events. * @see GetEventsFunction */ - getOptimisticMintingRequestedEvents: GetEvents.Function + getOptimisticMintingRequestedEvents: GetChainEvents.Function /** * Get emitted OptimisticMintingCancelled events. * @see GetEventsFunction */ - getOptimisticMintingCancelledEvents: GetEvents.Function + getOptimisticMintingCancelledEvents: GetChainEvents.Function /** * Get emitted OptimisticMintingFinalized events. * @see GetEventsFunction */ - getOptimisticMintingFinalizedEvents: GetEvents.Function + getOptimisticMintingFinalizedEvents: GetChainEvents.Function } /** @@ -132,7 +132,7 @@ export type OptimisticMintingRequestedEvent = { /** * Minter's chain identifier. */ - minter: Identifier + minter: ChainIdentifier /** * Unique deposit identifier. * @see Bridge.buildDepositKey @@ -141,7 +141,7 @@ export type OptimisticMintingRequestedEvent = { /** * Depositor's chain identifier. */ - depositor: Identifier + depositor: ChainIdentifier /** * Amount of tokens requested to mint. * This value is in ERC 1e18 precision, it has to be converted before using @@ -158,7 +158,7 @@ export type OptimisticMintingRequestedEvent = { * Index of an output in the funding transaction made to fund the deposit. */ fundingOutputIndex: number -} & Event +} & ChainEvent /** * Represents an event that is emitted when an optimistic minting request @@ -168,13 +168,13 @@ export type OptimisticMintingCancelledEvent = { /** * Guardian's chain identifier. */ - guardian: Identifier + guardian: ChainIdentifier /** * Unique deposit identifier. * @see Bridge.buildDepositKey */ depositKey: Hex -} & Event +} & ChainEvent /** * Represents an event that is emitted when an optimistic minting request @@ -184,7 +184,7 @@ export type OptimisticMintingFinalizedEvent = { /** * Minter's chain identifier. */ - minter: Identifier + minter: ChainIdentifier /** * Unique deposit identifier. * @see Bridge.buildDepositKey @@ -193,7 +193,7 @@ export type OptimisticMintingFinalizedEvent = { /** * Depositor's chain identifier. */ - depositor: Identifier + depositor: ChainIdentifier /** * Value of the new optimistic minting debt of the depositor. * This value is in ERC 1e18 precision, it has to be converted before using @@ -202,4 +202,4 @@ export type OptimisticMintingFinalizedEvent = { // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 // precision to Bitcoin in 1e8 precision (satoshi). optimisticMintingDebt: BigNumber -} & Event +} & ChainEvent diff --git a/typescript/src/lib/contracts/wallet-registry.ts b/typescript/src/lib/contracts/wallet-registry.ts index 969756a88..417e9c745 100644 --- a/typescript/src/lib/contracts/wallet-registry.ts +++ b/typescript/src/lib/contracts/wallet-registry.ts @@ -1,7 +1,7 @@ import { Hex } from "../utils" -import { Event, GetEvents } from "./chain-event" +import { ChainEvent, GetChainEvents } from "./chain-event" import { BigNumber } from "ethers" -import { Identifier } from "./chain-identifier" +import { ChainIdentifier } from "./chain-identifier" /** * Interface for communication with the WalletRegistry on-chain contract. @@ -18,19 +18,19 @@ export interface WalletRegistry { * Get emitted DkgResultSubmittedEvent events. * @see GetEventsFunction */ - getDkgResultSubmittedEvents: GetEvents.Function + getDkgResultSubmittedEvents: GetChainEvents.Function /** * Get emitted DkgResultApprovedEvent events. * @see GetEventsFunction */ - getDkgResultApprovedEvents: GetEvents.Function + getDkgResultApprovedEvents: GetChainEvents.Function /** * Get emitted DkgResultChallengedEvent events. * @see GetEventsFunction */ - getDkgResultChallengedEvents: GetEvents.Function + getDkgResultChallengedEvents: GetChainEvents.Function } /** @@ -50,7 +50,7 @@ export type DkgResultSubmittedEvent = { * DKG result object. */ result: DkgResult -} & Event +} & ChainEvent /** * Represents an event emitted when a DKG result is approved on the on-chain @@ -64,8 +64,8 @@ export type DkgResultApprovedEvent = { /** * Approver's chain identifier. */ - approver: Identifier -} & Event + approver: ChainIdentifier +} & ChainEvent /** * Represents an event emitted when a DKG result is challenged on the on-chain @@ -79,12 +79,12 @@ export type DkgResultChallengedEvent = { /** * Challenger's chain identifier. */ - challenger: Identifier + challenger: ChainIdentifier /** * Reason of the challenge. */ reason: string -} & Event +} & ChainEvent /** * Represents a DKG on-chain result. diff --git a/typescript/src/lib/ethereum/address.ts b/typescript/src/lib/ethereum/address.ts index cd9f1a054..6b45775c5 100644 --- a/typescript/src/lib/ethereum/address.ts +++ b/typescript/src/lib/ethereum/address.ts @@ -1,4 +1,4 @@ -import { Identifier as ChainIdentifier } from "../contracts" +import { ChainIdentifier } from "../contracts" import { utils } from "ethers" /** diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 03da23321..7d6b7d600 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -6,8 +6,8 @@ import { } from "../../../typechain/Bridge" import { Bridge as ChainBridge, - GetEvents, - Identifier as ChainIdentifier, + GetChainEvents, + ChainIdentifier, WalletRegistry as ChainWalletRegistry, NewWalletRegisteredEvent, Wallet, @@ -61,7 +61,7 @@ export class Bridge * @see {ChainBridge#getDepositRevealedEvents} */ async getDepositRevealedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events: EthersEvent[] = await this.getEvents( @@ -478,7 +478,7 @@ export class Bridge * @see {ChainBridge#getNewWalletRegisteredEvents} */ async getNewWalletRegisteredEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events: EthersEvent[] = await this.getEvents( @@ -583,7 +583,7 @@ export class Bridge * @see {ChainBridge#getDepositRevealedEvents} */ async getRedemptionRequestedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { // FIXME: Filtering by indexed walletPubKeyHash field may not work diff --git a/typescript/src/lib/ethereum/contract-handle.ts b/typescript/src/lib/ethereum/contract-handle.ts index 12ad04230..2faf1c674 100644 --- a/typescript/src/lib/ethereum/contract-handle.ts +++ b/typescript/src/lib/ethereum/contract-handle.ts @@ -4,7 +4,7 @@ import { Event as EthersEvent, EventFilter as EthersEventFilter, } from "@ethersproject/contracts" -import { GetEvents } from "../contracts" +import { GetChainEvents } from "../contracts" import { backoffRetrier, ExecutionLoggerFn, @@ -121,7 +121,7 @@ export class EthereumContract { */ async getEvents( eventName: string, - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { // TODO: Test if we need a workaround for querying events from big range in chunks, diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index 30c7eccec..43c206a3d 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -1,6 +1,6 @@ import { TBTCVault as ContractTBTCVault } from "../../../typechain/TBTCVault" import { - GetEvents, + GetChainEvents, TBTCVault as ChainTBTCVault, OptimisticMintingCancelledEvent, OptimisticMintingFinalizedEvent, @@ -196,7 +196,7 @@ export class TBTCVault * @see {ChainBridge#getOptimisticMintingRequestedEvents} */ async getOptimisticMintingRequestedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events = await this.getEvents( @@ -229,7 +229,7 @@ export class TBTCVault * @see {ChainBridge#getOptimisticMintingCancelledEvents} */ async getOptimisticMintingCancelledEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events = await this.getEvents( @@ -256,7 +256,7 @@ export class TBTCVault * @see {ChainBridge#getOptimisticMintingFinalizedEvents} */ async getOptimisticMintingFinalizedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events = await this.getEvents( diff --git a/typescript/src/lib/ethereum/wallet-registry.ts b/typescript/src/lib/ethereum/wallet-registry.ts index 859a01661..f893ead8f 100644 --- a/typescript/src/lib/ethereum/wallet-registry.ts +++ b/typescript/src/lib/ethereum/wallet-registry.ts @@ -1,6 +1,6 @@ import { WalletRegistry as ContractWalletRegistry } from "../../../typechain/WalletRegistry" import { - GetEvents, + GetChainEvents, WalletRegistry as ChainWalletRegistry, DkgResultApprovedEvent, DkgResultChallengedEvent, @@ -45,7 +45,7 @@ export class WalletRegistry * @see {ChainWalletRegistry#getDkgResultSubmittedEvents} */ async getDkgResultSubmittedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events: EthersEvent[] = await this.getEvents( @@ -88,7 +88,7 @@ export class WalletRegistry * @see {ChainWalletRegistry#getDkgResultApprovedEvents} */ async getDkgResultApprovedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events: EthersEvent[] = await this.getEvents( @@ -113,7 +113,7 @@ export class WalletRegistry * @see {ChainWalletRegistry#getDkgResultChallengedEvents} */ async getDkgResultChallengedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const events: EthersEvent[] = await this.getEvents( diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index aabdc0c92..7981adc7c 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -1,8 +1,8 @@ import { Bridge, WalletRegistry, - GetEvents, - Identifier, + GetChainEvents, + ChainIdentifier, NewWalletRegisteredEvent, Wallet, RedemptionRequest, @@ -50,7 +50,7 @@ interface RedemptionProofLogEntry { } interface NewWalletRegisteredEventsLog { - options?: GetEvents.Options + options?: GetChainEvents.Options filterArgs: unknown[] } @@ -125,7 +125,7 @@ export class MockBridge implements Bridge { } getDepositRevealedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { const deposit = depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0] @@ -158,7 +158,7 @@ export class MockBridge implements Bridge { sweepTx: BitcoinRawTxVectors, sweepProof: BitcoinSpvProof, mainUtxo: BitcoinUtxo, - vault?: Identifier + vault?: ChainIdentifier ): Promise { this._depositSweepProofLog.push({ sweepTx, sweepProof, mainUtxo }) return new Promise((resolve, _) => { @@ -340,7 +340,7 @@ export class MockBridge implements Bridge { } async getNewWalletRegisteredEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { this._newWalletRegisteredEventsLog.push({ options, filterArgs }) @@ -373,7 +373,7 @@ export class MockBridge implements Bridge { } getRedemptionRequestedEvents( - options?: GetEvents.Options, + options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { throw new Error("not implemented") From d7308781788f50e4f0e522d2e4ebc6c485e4df3f Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 26 Sep 2023 11:27:59 +0200 Subject: [PATCH 13/43] Final iteration on `lib/electrum` Here we make a final rework of the `lib/electrum` module. The main work is focused on improving component names. --- typescript/src/index.ts | 2 +- typescript/src/lib/electrum/client.ts | 38 ++++++++++++++------------- typescript/test/electrum.test.ts | 12 +++++---- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/typescript/src/index.ts b/typescript/src/index.ts index c522694f8..86ea36d1d 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -59,7 +59,7 @@ export { BitcoinNetwork, } from "./lib/bitcoin" -export { Client as ElectrumClient } from "./lib/electrum" +export { ElectrumClient } from "./lib/electrum" export { Bridge as EthereumBridge, diff --git a/typescript/src/lib/electrum/client.ts b/typescript/src/lib/electrum/client.ts index cf12baca5..c24b68d57 100644 --- a/typescript/src/lib/electrum/client.ts +++ b/typescript/src/lib/electrum/client.ts @@ -20,7 +20,7 @@ import { backoffRetrier, Hex, RetrierFn } from "../utils" /** * Represents a set of credentials required to establish an Electrum connection. */ -export interface Credentials { +export interface ElectrumCredentials { /** * Host pointing to the Electrum server. */ @@ -38,7 +38,7 @@ export interface Credentials { /** * Additional options used by the Electrum server. */ -export type ClientOptions = object +export type ElectrumClientOptions = object /** * Type for {@link Electrum} client from electrum-client-js library. @@ -50,21 +50,21 @@ type Electrum = any * is supposed to take a proper Electrum connection, do the work, and return * a promise holding the outcome of given type. */ -type Action = (electrum: Electrum) => Promise +type ElectrumAction = (electrum: Electrum) => Promise /** * Electrum-based implementation of the Bitcoin client. */ -export class Client implements BitcoinClient { - private credentials: Credentials[] - private options?: ClientOptions +export class ElectrumClient implements BitcoinClient { + private credentials: ElectrumCredentials[] + private options?: ElectrumClientOptions private totalRetryAttempts: number private retryBackoffStep: number private connectionTimeout: number constructor( - credentials: Credentials[], - options?: ClientOptions, + credentials: ElectrumCredentials[], + options?: ElectrumClientOptions, totalRetryAttempts = 3, retryBackoffStep = 10000, // 10 seconds connectionTimeout = 20000 // 20 seconds @@ -89,19 +89,19 @@ export class Client implements BitcoinClient { */ static fromUrl( url: string | string[], - options?: ClientOptions, + options?: ElectrumClientOptions, totalRetryAttempts = 3, retryBackoffStep = 1000, // 10 seconds connectionTimeout = 20000 // 20 seconds - ): Client { - let credentials: Credentials[] + ): ElectrumClient { + let credentials: ElectrumCredentials[] if (Array.isArray(url)) { credentials = url.map(this.parseElectrumCredentials) } else { credentials = [this.parseElectrumCredentials(url)] } - return new Client( + return new ElectrumClient( credentials, options, totalRetryAttempts, @@ -115,7 +115,7 @@ export class Client implements BitcoinClient { * @param url - URL to be parsed. * @returns Electrum credentials object. */ - private static parseElectrumCredentials(url: string): Credentials { + private static parseElectrumCredentials(url: string): ElectrumCredentials { const urlObj = new URL(url) return { @@ -136,8 +136,10 @@ export class Client implements BitcoinClient { * @param action - Action that makes use of the Electrum connection. * @returns Promise holding the outcome. */ - private async withElectrum(action: Action): Promise { - const connect = async (credentials: Credentials): Promise => { + private async withElectrum(action: ElectrumAction): Promise { + const connect = async ( + credentials: ElectrumCredentials + ): Promise => { const electrum: Electrum = new Electrum( credentials.host, credentials.port, @@ -237,7 +239,7 @@ export class Client implements BitcoinClient { const unspentTransactions: UnspentOutput[] = await this.withBackoffRetrier()(async () => { return await electrum.blockchain_scripthash_listunspent( - computeScriptHash(script) + computeElectrumScriptHash(script) ) }) @@ -265,7 +267,7 @@ export class Client implements BitcoinClient { HistoryItem[] >()(async () => { return await electrum.blockchain_scripthash_getHistory( - computeScriptHash(script) + computeElectrumScriptHash(script) ) }) @@ -547,7 +549,7 @@ export class Client implements BitcoinClient { * @param script - Bitcoin script as hex string * @returns Electrum script hash as a hex string. */ -export function computeScriptHash(script: string): string { +export function computeElectrumScriptHash(script: string): string { const _script = Hex.from(Buffer.from(script, "hex")).toPrefixedString() const hash256 = utils.sha256(_script) diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index 8d643309e..0775f4b5e 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -1,7 +1,7 @@ import { - Credentials as ElectrumCredentials, - Client as ElectrumClient, - computeScriptHash, + ElectrumCredentials, + ElectrumClient, + computeElectrumScriptHash, } from "../src/lib/electrum" import { BitcoinNetwork } from "../src/lib/bitcoin" import { @@ -219,13 +219,15 @@ describe("Electrum", () => { }) }) - describe("computeScriptHash", () => { + describe("computeElectrumScriptHash", () => { it("should convert Bitcoin script to an Electrum script hash correctly", () => { const script = "00144b47c798d12edd17dfb4ea98e5447926f664731c" const expectedScriptHash = "cabdea0bfc10fb3521721dde503487dd1f0e41dd6609da228066757563f292ab" - expect(computeScriptHash(script)).to.be.equal(expectedScriptHash) + expect(computeElectrumScriptHash(script)).to.be.equal( + expectedScriptHash + ) }) }) }) From 7478d3fd92ab431c99ee1569e04122f869d24142 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 26 Sep 2023 13:00:52 +0200 Subject: [PATCH 14/43] Final iteration on `lib/ethereum` Here we make a final rework of the `lib/ethereum` module. The main work is focused on improving component names and structure. --- typescript/src/index.ts | 10 +- typescript/src/lib/ethereum/adapter.ts | 307 ++++++++++++++++++ typescript/src/lib/ethereum/address.ts | 11 +- typescript/src/lib/ethereum/bridge.ts | 190 ++++++----- .../src/lib/ethereum/contract-handle.ts | 288 ---------------- typescript/src/lib/ethereum/index.ts | 4 +- typescript/src/lib/ethereum/tbtc-token.ts | 48 +-- typescript/src/lib/ethereum/tbtc-vault.ts | 70 ++-- .../src/lib/ethereum/wallet-registry.ts | 30 +- typescript/test/data/deposit-refund.ts | 14 +- typescript/test/data/deposit-sweep.ts | 26 +- typescript/test/data/redemption.ts | 50 ++- typescript/test/deposit.test.ts | 4 +- typescript/test/ethereum.test.ts | 48 ++- typescript/test/utils/mock-bridge.ts | 10 +- 15 files changed, 604 insertions(+), 506 deletions(-) create mode 100644 typescript/src/lib/ethereum/adapter.ts delete mode 100644 typescript/src/lib/ethereum/contract-handle.ts diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 86ea36d1d..dfc6b3bd1 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -62,11 +62,11 @@ export { export { ElectrumClient } from "./lib/electrum" export { - Bridge as EthereumBridge, - WalletRegistry as EthereumWalletRegistry, - Address as EthereumAddress, - TBTCVault as EthereumTBTCVault, - TBTCToken as EthereumTBTCToken, + EthereumBridge, + EthereumWalletRegistry, + EthereumAddress, + EthereumTBTCVault, + EthereumTBTCToken, } from "./lib/ethereum" export { Hex } from "./lib/utils" diff --git a/typescript/src/lib/ethereum/adapter.ts b/typescript/src/lib/ethereum/adapter.ts new file mode 100644 index 000000000..8a678acc3 --- /dev/null +++ b/typescript/src/lib/ethereum/adapter.ts @@ -0,0 +1,307 @@ +import { + ContractTransaction as EthersContractTransaction, + providers, + Signer, + utils, +} from "ethers" +import { + Contract as EthersContract, + Event as EthersEvent, + EventFilter as EthersEventFilter, +} from "@ethersproject/contracts" +import { GetChainEvents } from "../contracts" +import { + backoffRetrier, + ExecutionLoggerFn, + skipRetryWhenMatched, +} from "../utils" +import { EthereumAddress } from "./address" + +/** + * Contract deployment artifact. + * @see [hardhat-deploy#Deployment](https://github.com/wighawag/hardhat-deploy/blob/0c969e9a27b4eeff9f5ccac7e19721ef2329eed2/types.ts#L358)} + */ +export interface EthersContractDeployment { + /** + * Address of the deployed contract. + */ + address: string + /** + * Contract's ABI. + */ + abi: any[] + /** + * Deployment transaction receipt. + */ + receipt: { + /** + * Number of block in which the contract was deployed. + */ + blockNumber: number + } +} + +/** + * Represents a config set required to connect an Ethereum contract. + */ +export interface EthersContractConfig { + /** + * Address of the Ethereum contract as a 0x-prefixed hex string. + * Optional parameter, if not provided the value will be resolved from the + * contract artifact. + */ + address?: string + /** + * Signer - will return a Contract which will act on behalf of that signer. The signer will sign all contract transactions. + * Provider - will return a downgraded Contract which only has read-only access (i.e. constant calls) + */ + signerOrProvider: Signer | providers.Provider + /** + * Number of a block in which the contract was deployed. + * Optional parameter, if not provided the value will be resolved from the + * contract artifact. + */ + deployedAtBlockNumber?: number +} + +/** + * Ethers-based contract handle. + */ +export class EthersContractHandle { + /** + * Ethers instance of the deployed contract. + */ + protected readonly _instance: T + /** + * Number of a block within which the contract was deployed. Value is read from + * the contract deployment artifact. It can be overwritten by setting a + * {@link EthersContractConfig.deployedAtBlockNumber} property. + */ + protected readonly _deployedAtBlockNumber: number + /** + * Number of retries for ethereum requests. + */ + protected readonly _totalRetryAttempts: number + + /** + * @param config Configuration for contract instance initialization. + * @param deployment Contract Deployment artifact. + * @param totalRetryAttempts Number of retries for ethereum requests. + */ + constructor( + config: EthersContractConfig, + deployment: EthersContractDeployment, + totalRetryAttempts = 3 + ) { + this._instance = new EthersContract( + config.address ?? utils.getAddress(deployment.address), + `${JSON.stringify(deployment.abi)}`, + config.signerOrProvider + ) as T + + this._deployedAtBlockNumber = + config.deployedAtBlockNumber ?? deployment.receipt.blockNumber + + this._totalRetryAttempts = totalRetryAttempts + } + + /** + * Get address of the contract instance. + * @returns Address of this contract instance. + */ + getAddress(): EthereumAddress { + return EthereumAddress.from(this._instance.address) + } + + /** + * Get events emitted by the Ethereum contract. + * It starts searching from provided block number. If the {@link GetEvents.Options#fromBlock} + * option is missing it looks for a contract's defined property + * {@link _deployedAtBlockNumber}. If the property is missing starts searching + * from block `0`. + * @param eventName Name of the event. + * @param options Options for events fetching. + * @param filterArgs Arguments for events filtering. + * @returns Array of found events. + */ + async getEvents( + eventName: string, + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + // TODO: Test if we need a workaround for querying events from big range in chunks, + // see: https://github.com/keep-network/tbtc-monitoring/blob/e169357d7b8c638d4eaf73d52aa8f53ee4aebc1d/src/lib/ethereum-helper.js#L44-L73 + return backoffRetrier( + options?.retries ?? this._totalRetryAttempts + )(async () => { + return await EthersEventUtils.getEvents( + this._instance, + this._instance.filters[eventName](...filterArgs), + options?.fromBlock ?? this._deployedAtBlockNumber, + options?.toBlock, + options?.batchedQueryBlockInterval, + options?.logger + ) + }) + } +} + +/** + * Ethers-based utilities for transactions. + */ +export namespace EthersTransactionUtils { + /** + * Sends ethereum transaction with retries. + * @param fn Function to execute with retries. + * @param retries The number of retries to perform before bubbling the failure out. + * @param logger A logger function to pass execution messages. + * @param nonRetryableErrors List of error messages that if returned from executed + * function, should break the retry loop and return immediately. + * @returns Result of function execution. + * @throws An error returned by function execution. An error thrown by the executed + * function is processed by {@link resolveEthersError} function to resolve + * the revert message in case of a transaction revert. + */ + export async function sendWithRetry( + fn: () => Promise, + retries: number, + logger?: ExecutionLoggerFn, + nonRetryableErrors?: Array + ): Promise { + return backoffRetrier( + retries, + 1000, + logger, + nonRetryableErrors ? skipRetryWhenMatched(nonRetryableErrors) : undefined + )(async () => { + try { + return await fn() + } catch (err: unknown) { + throw resolveEthersError(err) + } + }) + } + + /** + * Represents an interface that matches the errors structure thrown by ethers library. + * {@see {@link https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/logger/src.ts/index.ts#L268-L277 ethers source code}} + */ + interface EthersError extends Error { + reason: string + code: string + error: unknown + } + + /** + * Takes an error and tries to resolve a revert message if the error is related + * to reverted transaction. + * @param err Error to process. + * @returns Error with a revert message or the input error when the error could + * not be resolved successfully. + */ + function resolveEthersError(err: unknown): unknown { + const isEthersError = (obj: any): obj is EthersError => { + return "reason" in obj && "code" in obj && "error" in obj + } + + if (isEthersError(err) && err !== null) { + // Ethers errors are nested. The parent UNPREDICTABLE_GAS_LIMIT has a general + // reason "cannot estimate gas; transaction may fail or may require manual gas limit", + if (err.code === "UNPREDICTABLE_GAS_LIMIT") { + if (typeof isEthersError(err["error"]) && err["error"] !== null) { + // The nested error is expected to contain a reason property with a message + // of the transaction revert. + return new Error((err["error"] as EthersError).reason) + } + } + } + + return err + } +} + +/** + * Ethers-based utilities for events. + */ +export namespace EthersEventUtils { + const GET_EVENTS_BLOCK_INTERVAL = 10_000 + + /** + * Looks up all existing events defined by the {@link event} filter on + * {@link sourceContract}, searching past blocks and then returning them. + * Does not wait for any new events. It starts searching from provided block number. + * If the {@link fromBlock} is missing it starts searching from block `0`. + * It pulls events in one `getLogs` call. If the call fails it fallbacks to querying + * events in batches of {@link batchedQueryBlockInterval} blocks. If the parameter + * is not set it queries in {@link GET_EVENTS_BLOCK_INTERVAL} blocks batches. + * @param sourceContract The contract instance that emits the event. + * @param event The event filter to query. + * @param fromBlock Starting block for events search. + * @param toBlock Ending block for events search. + * @param batchedQueryBlockInterval Block interval for batched events pulls. + * @param logger A logger function to pass execution messages. + * @returns A promise that will be fulfilled by the list of event objects once + * they are found. + */ + export async function getEvents( + sourceContract: EthersContract, + event: EthersEventFilter, + fromBlock: number = 0, + toBlock: number | string = "latest", + batchedQueryBlockInterval: number = GET_EVENTS_BLOCK_INTERVAL, + logger: ExecutionLoggerFn = console.debug + ): Promise { + return new Promise(async (resolve, reject) => { + let resultEvents: EthersEvent[] = [] + try { + resultEvents = await sourceContract.queryFilter( + event, + fromBlock, + toBlock + ) + } catch (err) { + logger( + `switching to partial events pulls; ` + + `failed to get events in one request from contract: [${event.address}], ` + + `fromBlock: [${fromBlock}], toBlock: [${toBlock}]: [${err}]` + ) + + try { + if (typeof toBlock === "string") { + toBlock = (await sourceContract.provider.getBlock(toBlock)).number + } + + let batchStartBlock = fromBlock + + while (batchStartBlock <= toBlock) { + let batchEndBlock = batchStartBlock + batchedQueryBlockInterval + if (batchEndBlock > toBlock) { + batchEndBlock = toBlock + } + logger( + `executing partial events pull from contract: [${event.address}], ` + + `fromBlock: [${batchStartBlock}], toBlock: [${batchEndBlock}]` + ) + const foundEvents = await sourceContract.queryFilter( + event, + batchStartBlock, + batchEndBlock + ) + + resultEvents = resultEvents.concat(foundEvents) + logger( + `fetched [${foundEvents.length}] events, has ` + + `[${resultEvents.length}] total` + ) + + batchStartBlock = batchEndBlock + 1 + } + } catch (error) { + return reject(error) + } + } + + return resolve(resultEvents) + }) + } +} diff --git a/typescript/src/lib/ethereum/address.ts b/typescript/src/lib/ethereum/address.ts index 6b45775c5..9135dddd0 100644 --- a/typescript/src/lib/ethereum/address.ts +++ b/typescript/src/lib/ethereum/address.ts @@ -5,11 +5,10 @@ import { utils } from "ethers" * Represents an Ethereum address. */ // TODO: Make Address extends Hex -export class Address implements ChainIdentifier { +export class EthereumAddress implements ChainIdentifier { readonly identifierHex: string - // TODO: Make constructor private - constructor(address: string) { + private constructor(address: string) { let validAddress: string try { @@ -21,12 +20,12 @@ export class Address implements ChainIdentifier { this.identifierHex = validAddress.substring(2).toLowerCase() } - static from(address: string): Address { - return new Address(address) + static from(address: string): EthereumAddress { + return new EthereumAddress(address) } // TODO: Remove once extends Hex - equals(otherValue: Address): boolean { + equals(otherValue: EthereumAddress): boolean { return this.identifierHex === otherValue.identifierHex } } diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 7d6b7d600..1d424dd1b 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -1,14 +1,14 @@ import { - Bridge as ContractBridge, - Deposit as ContractDeposit, - Redemption as ContractRedemption, - Wallets, + Bridge as BridgeTypechain, + Deposit as DepositTypechain, + Redemption as RedemptionTypechain, + Wallets as WalletsTypechain, } from "../../../typechain/Bridge" import { - Bridge as ChainBridge, + Bridge, GetChainEvents, ChainIdentifier, - WalletRegistry as ChainWalletRegistry, + WalletRegistry, NewWalletRegisteredEvent, Wallet, WalletState, @@ -31,34 +31,34 @@ import { BitcoinUtxo, } from "../bitcoin" import { - ContractConfig, - EthereumContract, - sendWithRetry, -} from "./contract-handle" + EthersContractConfig, + EthersContractHandle, + EthersTransactionUtils, +} from "./adapter" import BridgeDeployment from "@keep-network/tbtc-v2/artifacts/Bridge.json" -import { Address } from "./address" -import { WalletRegistry } from "./wallet-registry" +import { EthereumAddress } from "./address" +import { EthereumWalletRegistry } from "./wallet-registry" -type ContractDepositRequest = ContractDeposit.DepositRequestStructOutput +type DepositRequestTypechain = DepositTypechain.DepositRequestStructOutput -type ContractRedemptionRequest = - ContractRedemption.RedemptionRequestStructOutput +type RedemptionRequestTypechain = + RedemptionTypechain.RedemptionRequestStructOutput /** * Implementation of the Ethereum Bridge handle. - * @see {ChainBridge} for reference. + * @see {Bridge} for reference. */ -export class Bridge - extends EthereumContract - implements ChainBridge +export class EthereumBridge + extends EthersContractHandle + implements Bridge { - constructor(config: ContractConfig) { + constructor(config: EthersContractConfig) { super(config, BridgeDeployment) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#getDepositRevealedEvents} + * @see {Bridge#getDepositRevealedEvents} */ async getDepositRevealedEvents( options?: GetChainEvents.Options, @@ -79,7 +79,7 @@ export class Bridge fundingOutputIndex: BigNumber.from( event.args!.fundingOutputIndex ).toNumber(), - depositor: new Address(event.args!.depositor), + depositor: EthereumAddress.from(event.args!.depositor), amount: BigNumber.from(event.args!.amount), blindingFactor: Hex.from(event.args!.blindingFactor).toString(), walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(), @@ -88,53 +88,53 @@ export class Bridge vault: event.args!.vault === constants.AddressZero ? undefined - : new Address(event.args!.vault), + : EthereumAddress.from(event.args!.vault), } }) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#pendingRedemptions} + * @see {Bridge#pendingRedemptions} */ async pendingRedemptions( walletPublicKey: string, redeemerOutputScript: string ): Promise { - const redemptionKey = Bridge.buildRedemptionKey( + const redemptionKey = EthereumBridge.buildRedemptionKey( BitcoinHashUtils.computeHash160(walletPublicKey), redeemerOutputScript ) - const request: ContractRedemptionRequest = - await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.pendingRedemptions(redemptionKey) - } - ) + const request: RedemptionRequestTypechain = + await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.pendingRedemptions(redemptionKey) + }) return this.parseRedemptionRequest(request, redeemerOutputScript) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#timedOutRedemptions} + * @see {Bridge#timedOutRedemptions} */ async timedOutRedemptions( walletPublicKey: string, redeemerOutputScript: string ): Promise { - const redemptionKey = Bridge.buildRedemptionKey( + const redemptionKey = EthereumBridge.buildRedemptionKey( BitcoinHashUtils.computeHash160(walletPublicKey), redeemerOutputScript ) - const request: ContractRedemptionRequest = - await backoffRetrier(this._totalRetryAttempts)( - async () => { - return await this._instance.timedOutRedemptions(redemptionKey) - } - ) + const request: RedemptionRequestTypechain = + await backoffRetrier( + this._totalRetryAttempts + )(async () => { + return await this._instance.timedOutRedemptions(redemptionKey) + }) return this.parseRedemptionRequest(request, redeemerOutputScript) } @@ -180,11 +180,11 @@ export class Bridge * @returns Parsed redemption request. */ private parseRedemptionRequest( - request: ContractRedemptionRequest, + request: RedemptionRequestTypechain, redeemerOutputScript: string ): RedemptionRequest { return { - redeemer: new Address(request.redeemer), + redeemer: EthereumAddress.from(request.redeemer), redeemerOutputScript: redeemerOutputScript, requestedAmount: BigNumber.from(request.requestedAmount), treasuryFee: BigNumber.from(request.treasuryFee), @@ -195,7 +195,7 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#revealDeposit} + * @see {Bridge#revealDeposit} */ async revealDeposit( depositTx: BitcoinRawTxVectors, @@ -219,7 +219,7 @@ export class Bridge vault: vault ? `0x${vault.identifierHex}` : constants.AddressZero, } - const tx = await sendWithRetry( + const tx = await EthersTransactionUtils.sendWithRetry( async () => { return await this._instance.revealDeposit(depositTxParam, revealParam) }, @@ -233,7 +233,7 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#submitDepositSweepProof} + * @see {Bridge#submitDepositSweepProof} */ async submitDepositSweepProof( sweepTx: BitcoinRawTxVectors, @@ -266,19 +266,22 @@ export class Bridge ? `0x${vault.identifierHex}` : constants.AddressZero - await sendWithRetry(async () => { - return await this._instance.submitDepositSweepProof( - sweepTxParam, - sweepProofParam, - mainUtxoParam, - vaultParam - ) - }, this._totalRetryAttempts) + await EthersTransactionUtils.sendWithRetry( + async () => { + return await this._instance.submitDepositSweepProof( + sweepTxParam, + sweepProofParam, + mainUtxoParam, + vaultParam + ) + }, + this._totalRetryAttempts + ) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#txProofDifficultyFactor} + * @see {Bridge#txProofDifficultyFactor} */ async txProofDifficultyFactor(): Promise { const txProofDifficultyFactor: BigNumber = await backoffRetrier( @@ -292,7 +295,7 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#requestRedemption} + * @see {Bridge#requestRedemption} */ async requestRedemption( walletPublicKey: string, @@ -320,19 +323,22 @@ export class Bridge rawRedeemerOutputScript, ]).toString("hex")}` - await sendWithRetry(async () => { - return await this._instance.requestRedemption( - walletPublicKeyHash, - mainUtxoParam, - prefixedRawRedeemerOutputScript, - amount - ) - }, this._totalRetryAttempts) + await EthersTransactionUtils.sendWithRetry( + async () => { + return await this._instance.requestRedemption( + walletPublicKeyHash, + mainUtxoParam, + prefixedRawRedeemerOutputScript, + amount + ) + }, + this._totalRetryAttempts + ) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#submitRedemptionProof} + * @see {Bridge#submitRedemptionProof} */ async submitRedemptionProof( redemptionTx: BitcoinRawTxVectors, @@ -365,28 +371,34 @@ export class Bridge walletPublicKey )}` - await sendWithRetry(async () => { - return await this._instance.submitRedemptionProof( - redemptionTxParam, - redemptionProofParam, - mainUtxoParam, - walletPublicKeyHash - ) - }, this._totalRetryAttempts) + await EthersTransactionUtils.sendWithRetry( + async () => { + return await this._instance.submitRedemptionProof( + redemptionTxParam, + redemptionProofParam, + mainUtxoParam, + walletPublicKeyHash + ) + }, + this._totalRetryAttempts + ) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#deposits} + * @see {Bridge#deposits} */ async deposits( depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { - const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) + const depositKey = EthereumBridge.buildDepositKey( + depositTxHash, + depositOutputIndex + ) - const deposit: ContractDepositRequest = - await backoffRetrier(this._totalRetryAttempts)( + const deposit: DepositRequestTypechain = + await backoffRetrier(this._totalRetryAttempts)( async () => { return await this._instance.deposits(depositKey) } @@ -422,15 +434,15 @@ export class Bridge * @returns Parsed revealed deposit. */ private parseRevealedDeposit( - deposit: ContractDepositRequest + deposit: DepositRequestTypechain ): RevealedDeposit { return { - depositor: new Address(deposit.depositor), + depositor: EthereumAddress.from(deposit.depositor), amount: BigNumber.from(deposit.amount), vault: deposit.vault === constants.AddressZero ? undefined - : new Address(deposit.vault), + : EthereumAddress.from(deposit.vault), revealedAt: BigNumber.from(deposit.revealedAt).toNumber(), sweptAt: BigNumber.from(deposit.sweptAt).toNumber(), treasuryFee: BigNumber.from(deposit.treasuryFee), @@ -439,7 +451,7 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#activeWalletPublicKey} + * @see {Bridge#activeWalletPublicKey} */ async activeWalletPublicKey(): Promise { const activeWalletPublicKeyHash: string = await backoffRetrier( @@ -475,7 +487,7 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#getNewWalletRegisteredEvents} + * @see {Bridge#getNewWalletRegisteredEvents} */ async getNewWalletRegisteredEvents( options?: GetChainEvents.Options, @@ -500,16 +512,16 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#walletRegistry} + * @see {Bridge#walletRegistry} */ - async walletRegistry(): Promise { + async walletRegistry(): Promise { const { ecdsaWalletRegistry } = await backoffRetrier<{ ecdsaWalletRegistry: string }>(this._totalRetryAttempts)(async () => { return await this._instance.contractReferences() }) - return new WalletRegistry({ + return new EthereumWalletRegistry({ address: ecdsaWalletRegistry, signerOrProvider: this._instance.signer || this._instance.provider, }) @@ -517,10 +529,10 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#wallets} + * @see {Bridge#wallets} */ async wallets(walletPublicKeyHash: Hex): Promise { - const wallet = await backoffRetrier( + const wallet = await backoffRetrier( this._totalRetryAttempts )(async () => { return await this._instance.wallets( @@ -537,7 +549,7 @@ export class Bridge * @returns Parsed wallet data. */ private async parseWalletDetails( - wallet: Wallets.WalletStructOutput + wallet: WalletsTypechain.WalletStructOutput ): Promise { const ecdsaWalletID = Hex.from(wallet.ecdsaWalletID) @@ -563,7 +575,7 @@ export class Bridge * Builds the UTXO hash based on the UTXO components. UTXO hash is computed as * `keccak256(txHash | txOutputIndex | txOutputValue)`. * - * @see {ChainBridge#buildUtxoHash} + * @see {Bridge#buildUtxoHash} */ buildUtxoHash(utxo: BitcoinUtxo): Hex { return Hex.from( @@ -580,7 +592,7 @@ export class Bridge // eslint-disable-next-line valid-jsdoc /** - * @see {ChainBridge#getDepositRevealedEvents} + * @see {Bridge#getDepositRevealedEvents} */ async getRedemptionRequestedEvents( options?: GetChainEvents.Options, @@ -614,7 +626,7 @@ export class Bridge blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), walletPublicKeyHash: Hex.from(event.args!.walletPubKeyHash).toString(), - redeemer: new Address(event.args!.redeemer), + redeemer: EthereumAddress.from(event.args!.redeemer), redeemerOutputScript: redeemerOutputScript, requestedAmount: BigNumber.from(event.args!.requestedAmount), treasuryFee: BigNumber.from(event.args!.treasuryFee), diff --git a/typescript/src/lib/ethereum/contract-handle.ts b/typescript/src/lib/ethereum/contract-handle.ts deleted file mode 100644 index 2faf1c674..000000000 --- a/typescript/src/lib/ethereum/contract-handle.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { providers, Signer, utils } from "ethers" -import { - Contract as EthersContract, - Event as EthersEvent, - EventFilter as EthersEventFilter, -} from "@ethersproject/contracts" -import { GetChainEvents } from "../contracts" -import { - backoffRetrier, - ExecutionLoggerFn, - skipRetryWhenMatched, -} from "../utils" -import { Address } from "./address" - -/** - * Contract deployment artifact. - * @see [hardhat-deploy#Deployment](https://github.com/wighawag/hardhat-deploy/blob/0c969e9a27b4eeff9f5ccac7e19721ef2329eed2/types.ts#L358)} - */ -export interface Deployment { - /** - * Address of the deployed contract. - */ - address: string - /** - * Contract's ABI. - */ - abi: any[] - /** - * Deployment transaction receipt. - */ - receipt: { - /** - * Number of block in which the contract was deployed. - */ - blockNumber: number - } -} - -/** - * Represents a config set required to connect an Ethereum contract. - */ -export interface ContractConfig { - /** - * Address of the Ethereum contract as a 0x-prefixed hex string. - * Optional parameter, if not provided the value will be resolved from the - * contract artifact. - */ - address?: string - /** - * Signer - will return a Contract which will act on behalf of that signer. The signer will sign all contract transactions. - * Provider - will return a downgraded Contract which only has read-only access (i.e. constant calls) - */ - signerOrProvider: Signer | providers.Provider - /** - * Number of a block in which the contract was deployed. - * Optional parameter, if not provided the value will be resolved from the - * contract artifact. - */ - deployedAtBlockNumber?: number -} - -/** - * Deployed Ethereum contract - */ -export class EthereumContract { - /** - * Ethers instance of the deployed contract. - */ - protected readonly _instance: T - /** - * Number of a block within which the contract was deployed. Value is read from - * the contract deployment artifact. It can be overwritten by setting a - * {@link ContractConfig.deployedAtBlockNumber} property. - */ - protected readonly _deployedAtBlockNumber: number - /** - * Number of retries for ethereum requests. - */ - protected readonly _totalRetryAttempts: number - - /** - * @param config Configuration for contract instance initialization. - * @param deployment Contract Deployment artifact. - * @param totalRetryAttempts Number of retries for ethereum requests. - */ - constructor( - config: ContractConfig, - deployment: Deployment, - totalRetryAttempts = 3 - ) { - this._instance = new EthersContract( - config.address ?? utils.getAddress(deployment.address), - `${JSON.stringify(deployment.abi)}`, - config.signerOrProvider - ) as T - - this._deployedAtBlockNumber = - config.deployedAtBlockNumber ?? deployment.receipt.blockNumber - - this._totalRetryAttempts = totalRetryAttempts - } - - /** - * Get address of the contract instance. - * @returns Address of this contract instance. - */ - getAddress(): Address { - return Address.from(this._instance.address) - } - - /** - * Get events emitted by the Ethereum contract. - * It starts searching from provided block number. If the {@link GetEvents.Options#fromBlock} - * option is missing it looks for a contract's defined property - * {@link _deployedAtBlockNumber}. If the property is missing starts searching - * from block `0`. - * @param eventName Name of the event. - * @param options Options for events fetching. - * @param filterArgs Arguments for events filtering. - * @returns Array of found events. - */ - async getEvents( - eventName: string, - options?: GetChainEvents.Options, - ...filterArgs: Array - ): Promise { - // TODO: Test if we need a workaround for querying events from big range in chunks, - // see: https://github.com/keep-network/tbtc-monitoring/blob/e169357d7b8c638d4eaf73d52aa8f53ee4aebc1d/src/lib/ethereum-helper.js#L44-L73 - return backoffRetrier( - options?.retries ?? this._totalRetryAttempts - )(async () => { - return await getEvents( - this._instance, - this._instance.filters[eventName](...filterArgs), - options?.fromBlock ?? this._deployedAtBlockNumber, - options?.toBlock, - options?.batchedQueryBlockInterval, - options?.logger - ) - }) - } -} - -/** - * Sends ethereum transaction with retries. - * @param fn Function to execute with retries. - * @param retries The number of retries to perform before bubbling the failure out. - * @param logger A logger function to pass execution messages. - * @param nonRetryableErrors List of error messages that if returned from executed - * function, should break the retry loop and return immediately. - * @returns Result of function execution. - * @throws An error returned by function execution. An error thrown by the executed - * function is processed by {@link resolveEthersError} function to resolve - * the revert message in case of a transaction revert. - */ -export async function sendWithRetry( - fn: () => Promise, - retries: number, - logger?: ExecutionLoggerFn, - nonRetryableErrors?: Array -): Promise { - return backoffRetrier( - retries, - 1000, - logger, - nonRetryableErrors ? skipRetryWhenMatched(nonRetryableErrors) : undefined - )(async () => { - try { - return await fn() - } catch (err: unknown) { - throw resolveEthersError(err) - } - }) -} - -/** - * Represents an interface that matches the errors structure thrown by ethers library. - * {@see {@link https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/logger/src.ts/index.ts#L268-L277 ethers source code}} - */ -interface EthersError extends Error { - reason: string - code: string - error: unknown -} - -/** - * Takes an error and tries to resolve a revert message if the error is related - * to reverted transaction. - * @param err Error to process. - * @returns Error with a revert message or the input error when the error could - * not be resolved successfully. - */ -function resolveEthersError(err: unknown): unknown { - const isEthersError = (obj: any): obj is EthersError => { - return "reason" in obj && "code" in obj && "error" in obj - } - - if (isEthersError(err) && err !== null) { - // Ethers errors are nested. The parent UNPREDICTABLE_GAS_LIMIT has a general - // reason "cannot estimate gas; transaction may fail or may require manual gas limit", - if (err.code === "UNPREDICTABLE_GAS_LIMIT") { - if (typeof isEthersError(err["error"]) && err["error"] !== null) { - // The nested error is expected to contain a reason property with a message - // of the transaction revert. - return new Error((err["error"] as EthersError).reason) - } - } - } - - return err -} - -const GET_EVENTS_BLOCK_INTERVAL = 10_000 - -/** - * Looks up all existing events defined by the {@link event} filter on - * {@link sourceContract}, searching past blocks and then returning them. - * Does not wait for any new events. It starts searching from provided block number. - * If the {@link fromBlock} is missing it starts searching from block `0`. - * It pulls events in one `getLogs` call. If the call fails it fallbacks to querying - * events in batches of {@link batchedQueryBlockInterval} blocks. If the parameter - * is not set it queries in {@link GET_EVENTS_BLOCK_INTERVAL} blocks batches. - * @param sourceContract The contract instance that emits the event. - * @param event The event filter to query. - * @param fromBlock Starting block for events search. - * @param toBlock Ending block for events search. - * @param batchedQueryBlockInterval Block interval for batched events pulls. - * @param logger A logger function to pass execution messages. - * @returns A promise that will be fulfilled by the list of event objects once - * they are found. - */ -async function getEvents( - sourceContract: EthersContract, - event: EthersEventFilter, - fromBlock: number = 0, - toBlock: number | string = "latest", - batchedQueryBlockInterval: number = GET_EVENTS_BLOCK_INTERVAL, - logger: ExecutionLoggerFn = console.debug -): Promise { - return new Promise(async (resolve, reject) => { - let resultEvents: EthersEvent[] = [] - try { - resultEvents = await sourceContract.queryFilter(event, fromBlock, toBlock) - } catch (err) { - logger( - `switching to partial events pulls; ` + - `failed to get events in one request from contract: [${event.address}], ` + - `fromBlock: [${fromBlock}], toBlock: [${toBlock}]: [${err}]` - ) - - try { - if (typeof toBlock === "string") { - toBlock = (await sourceContract.provider.getBlock(toBlock)).number - } - - let batchStartBlock = fromBlock - - while (batchStartBlock <= toBlock) { - let batchEndBlock = batchStartBlock + batchedQueryBlockInterval - if (batchEndBlock > toBlock) { - batchEndBlock = toBlock - } - logger( - `executing partial events pull from contract: [${event.address}], ` + - `fromBlock: [${batchStartBlock}], toBlock: [${batchEndBlock}]` - ) - const foundEvents = await sourceContract.queryFilter( - event, - batchStartBlock, - batchEndBlock - ) - - resultEvents = resultEvents.concat(foundEvents) - logger( - `fetched [${foundEvents.length}] events, has ` + - `[${resultEvents.length}] total` - ) - - batchStartBlock = batchEndBlock + 1 - } - } catch (error) { - return reject(error) - } - } - - return resolve(resultEvents) - }) -} diff --git a/typescript/src/lib/ethereum/index.ts b/typescript/src/lib/ethereum/index.ts index e6ac22fcf..5e822f8f1 100644 --- a/typescript/src/lib/ethereum/index.ts +++ b/typescript/src/lib/ethereum/index.ts @@ -4,7 +4,7 @@ export * from "./tbtc-token" export * from "./tbtc-vault" export * from "./wallet-registry" -// The `contract-handle` module should not be re-exported directly as it +// The `adapter` module should not be re-exported directly as it // contains low-level contract integration code. Re-export only components // that are relevant for `lib/ethereum` clients. -export { ContractConfig } from "./contract-handle" +export { EthersContractConfig as EthereumContractConfig } from "./adapter" diff --git a/typescript/src/lib/ethereum/tbtc-token.ts b/typescript/src/lib/ethereum/tbtc-token.ts index 825a8c920..cb174a045 100644 --- a/typescript/src/lib/ethereum/tbtc-token.ts +++ b/typescript/src/lib/ethereum/tbtc-token.ts @@ -1,30 +1,31 @@ -import { TBTC as ContractTBTC } from "../../../typechain/TBTC" -import { TBTCToken as ChainTBTCToken } from "../contracts" +import { TBTC as TBTCTypechain } from "../../../typechain/TBTC" +import { TBTCToken } from "../contracts" import { BigNumber, ContractTransaction, utils } from "ethers" import { BitcoinHashUtils, BitcoinUtxo } from "../bitcoin" import { Hex } from "../utils" import { - ContractConfig, - EthereumContract, - sendWithRetry, -} from "./contract-handle" + EthersContractConfig, + EthersContractHandle, + EthersTransactionUtils, +} from "./adapter" import TBTCDeployment from "@keep-network/tbtc-v2/artifacts/TBTC.json" -import { Address } from "./address" +import { EthereumAddress } from "./address" /** * Implementation of the Ethereum TBTC v2 token handle. + * @see {TBTCToken} for reference. */ -export class TBTCToken - extends EthereumContract - implements ChainTBTCToken +export class EthereumTBTCToken + extends EthersContractHandle + implements TBTCToken { - constructor(config: ContractConfig) { + constructor(config: EthersContractConfig) { super(config, TBTCDeployment) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCToken#totalSupply} + * @see {TBTCToken#totalSupply} */ async totalSupply(blockNumber?: number): Promise { return this._instance.totalSupply({ @@ -34,7 +35,7 @@ export class TBTCToken // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCToken#requestRedemption} + * @see {TBTCToken#requestRedemption} */ async requestRedemption( walletPublicKey: string, @@ -49,25 +50,28 @@ export class TBTCToken const vault = await this._instance.owner() const extraData = this.buildRequestRedemptionData( - Address.from(redeemer), + EthereumAddress.from(redeemer), walletPublicKey, mainUtxo, redeemerOutputScript ) - const tx = await sendWithRetry(async () => { - return await this._instance.approveAndCall( - vault, - amount, - extraData.toPrefixedString() - ) - }, this._totalRetryAttempts) + const tx = await EthersTransactionUtils.sendWithRetry( + async () => { + return await this._instance.approveAndCall( + vault, + amount, + extraData.toPrefixedString() + ) + }, + this._totalRetryAttempts + ) return Hex.from(tx.hash) } private buildRequestRedemptionData( - redeemer: Address, + redeemer: EthereumAddress, walletPublicKey: string, mainUtxo: BitcoinUtxo, redeemerOutputScript: string diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index 43c206a3d..20a60590b 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -1,7 +1,7 @@ -import { TBTCVault as ContractTBTCVault } from "../../../typechain/TBTCVault" +import { TBTCVault as TBTCVaultTypechain } from "../../../typechain/TBTCVault" import { GetChainEvents, - TBTCVault as ChainTBTCVault, + TBTCVault, OptimisticMintingCancelledEvent, OptimisticMintingFinalizedEvent, OptimisticMintingRequest, @@ -12,12 +12,12 @@ import { BitcoinTxHash } from "../bitcoin" import { backoffRetrier, Hex } from "../utils" import TBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import { - ContractConfig, - EthereumContract, - sendWithRetry, -} from "./contract-handle" -import { Address } from "./address" -import { Bridge } from "./bridge" + EthersContractConfig, + EthersContractHandle, + EthersTransactionUtils, +} from "./adapter" +import { EthereumAddress } from "./address" +import { EthereumBridge } from "./bridge" type ContractOptimisticMintingRequest = { requestedAt: BigNumber @@ -26,18 +26,19 @@ type ContractOptimisticMintingRequest = { /** * Implementation of the Ethereum TBTCVault handle. + * @see {TBTCVault} for reference. */ -export class TBTCVault - extends EthereumContract - implements ChainTBTCVault +export class EthereumTBTCVault + extends EthersContractHandle + implements TBTCVault { - constructor(config: ContractConfig) { + constructor(config: EthersContractConfig) { super(config, TBTCVaultDeployment) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#optimisticMintingDelay} + * @see {TBTCVault#optimisticMintingDelay} */ async optimisticMintingDelay(): Promise { const delaySeconds = await backoffRetrier(this._totalRetryAttempts)( @@ -51,23 +52,23 @@ export class TBTCVault // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#getMinters} + * @see {TBTCVault#getMinters} */ - async getMinters(): Promise { + async getMinters(): Promise { const minters: string[] = await backoffRetrier( this._totalRetryAttempts )(async () => { return await this._instance.getMinters() }) - return minters.map(Address.from) + return minters.map(EthereumAddress.from) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#isMinter} + * @see {TBTCVault#isMinter} */ - async isMinter(address: Address): Promise { + async isMinter(address: EthereumAddress): Promise { return await backoffRetrier(this._totalRetryAttempts)(async () => { return await this._instance.isMinter(`0x${address.identifierHex}`) }) @@ -75,9 +76,9 @@ export class TBTCVault // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#isGuardian} + * @see {TBTCVault#isGuardian} */ - async isGuardian(address: Address): Promise { + async isGuardian(address: EthereumAddress): Promise { return await backoffRetrier(this._totalRetryAttempts)(async () => { return await this._instance.isGuardian(`0x${address.identifierHex}`) }) @@ -85,13 +86,13 @@ export class TBTCVault // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#requestOptimisticMint} + * @see {TBTCVault#requestOptimisticMint} */ async requestOptimisticMint( depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { - const tx = await sendWithRetry( + const tx = await EthersTransactionUtils.sendWithRetry( async () => { return await this._instance.requestOptimisticMint( depositTxHash.reverse().toPrefixedString(), @@ -111,13 +112,13 @@ export class TBTCVault // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#cancelOptimisticMint} + * @see {TBTCVault#cancelOptimisticMint} */ async cancelOptimisticMint( depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { - const tx = await sendWithRetry( + const tx = await EthersTransactionUtils.sendWithRetry( async () => { return await this._instance.cancelOptimisticMint( depositTxHash.reverse().toPrefixedString(), @@ -134,13 +135,13 @@ export class TBTCVault // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#finalizeOptimisticMint} + * @see {TBTCVault#finalizeOptimisticMint} */ async finalizeOptimisticMint( depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { - const tx = await sendWithRetry( + const tx = await EthersTransactionUtils.sendWithRetry( async () => { return await this._instance.finalizeOptimisticMint( depositTxHash.reverse().toPrefixedString(), @@ -160,13 +161,16 @@ export class TBTCVault // eslint-disable-next-line valid-jsdoc /** - * @see {ChainTBTCVault#optimisticMintingRequests} + * @see {TBTCVault#optimisticMintingRequests} */ async optimisticMintingRequests( depositTxHash: BitcoinTxHash, depositOutputIndex: number ): Promise { - const depositKey = Bridge.buildDepositKey(depositTxHash, depositOutputIndex) + const depositKey = EthereumBridge.buildDepositKey( + depositTxHash, + depositOutputIndex + ) const request: ContractOptimisticMintingRequest = await backoffRetrier( @@ -210,11 +214,11 @@ export class TBTCVault blockNumber: BigNumber.from(event.blockNumber).toNumber(), blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), - minter: new Address(event.args!.minter), + minter: EthereumAddress.from(event.args!.minter), depositKey: Hex.from( BigNumber.from(event.args!.depositKey).toHexString() ), - depositor: new Address(event.args!.depositor), + depositor: EthereumAddress.from(event.args!.depositor), amount: BigNumber.from(event.args!.amount), fundingTxHash: BitcoinTxHash.from(event.args!.fundingTxHash).reverse(), fundingOutputIndex: BigNumber.from( @@ -243,7 +247,7 @@ export class TBTCVault blockNumber: BigNumber.from(event.blockNumber).toNumber(), blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), - guardian: new Address(event.args!.guardian), + guardian: EthereumAddress.from(event.args!.guardian), depositKey: Hex.from( BigNumber.from(event.args!.depositKey).toHexString() ), @@ -270,11 +274,11 @@ export class TBTCVault blockNumber: BigNumber.from(event.blockNumber).toNumber(), blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), - minter: new Address(event.args!.minter), + minter: EthereumAddress.from(event.args!.minter), depositKey: Hex.from( BigNumber.from(event.args!.depositKey).toHexString() ), - depositor: new Address(event.args!.depositor), + depositor: EthereumAddress.from(event.args!.depositor), optimisticMintingDebt: BigNumber.from( event.args!.optimisticMintingDebt ), diff --git a/typescript/src/lib/ethereum/wallet-registry.ts b/typescript/src/lib/ethereum/wallet-registry.ts index f893ead8f..a71940e78 100644 --- a/typescript/src/lib/ethereum/wallet-registry.ts +++ b/typescript/src/lib/ethereum/wallet-registry.ts @@ -1,7 +1,7 @@ -import { WalletRegistry as ContractWalletRegistry } from "../../../typechain/WalletRegistry" +import { WalletRegistry as WalletRegistryTypechain } from "../../../typechain/WalletRegistry" import { GetChainEvents, - WalletRegistry as ChainWalletRegistry, + WalletRegistry, DkgResultApprovedEvent, DkgResultChallengedEvent, DkgResultSubmittedEvent, @@ -10,24 +10,24 @@ import { backoffRetrier, Hex } from "../utils" import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber } from "ethers" import WalletRegistryDeployment from "@keep-network/ecdsa/artifacts/WalletRegistry.json" -import { ContractConfig, EthereumContract } from "./contract-handle" -import { Address } from "./address" +import { EthersContractConfig, EthersContractHandle } from "./adapter" +import { EthereumAddress } from "./address" /** * Implementation of the Ethereum WalletRegistry handle. - * @see {ChainWalletRegistry} for reference. + * @see {WalletRegistry} for reference. */ -export class WalletRegistry - extends EthereumContract - implements ChainWalletRegistry +export class EthereumWalletRegistry + extends EthersContractHandle + implements WalletRegistry { - constructor(config: ContractConfig) { + constructor(config: EthersContractConfig) { super(config, WalletRegistryDeployment) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainWalletRegistry#getWalletPublicKey} + * @see {WalletRegistry#getWalletPublicKey} */ async getWalletPublicKey(walletID: Hex): Promise { const publicKey = await backoffRetrier(this._totalRetryAttempts)( @@ -42,7 +42,7 @@ export class WalletRegistry // eslint-disable-next-line valid-jsdoc /** - * @see {ChainWalletRegistry#getDkgResultSubmittedEvents} + * @see {WalletRegistry#getDkgResultSubmittedEvents} */ async getDkgResultSubmittedEvents( options?: GetChainEvents.Options, @@ -85,7 +85,7 @@ export class WalletRegistry // eslint-disable-next-line valid-jsdoc /** - * @see {ChainWalletRegistry#getDkgResultApprovedEvents} + * @see {WalletRegistry#getDkgResultApprovedEvents} */ async getDkgResultApprovedEvents( options?: GetChainEvents.Options, @@ -103,14 +103,14 @@ export class WalletRegistry blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), resultHash: Hex.from(event.args!.resultHash), - approver: Address.from(event.args!.approver), + approver: EthereumAddress.from(event.args!.approver), } }) } // eslint-disable-next-line valid-jsdoc /** - * @see {ChainWalletRegistry#getDkgResultChallengedEvents} + * @see {WalletRegistry#getDkgResultChallengedEvents} */ async getDkgResultChallengedEvents( options?: GetChainEvents.Options, @@ -128,7 +128,7 @@ export class WalletRegistry blockHash: Hex.from(event.blockHash), transactionHash: Hex.from(event.transactionHash), resultHash: Hex.from(event.args!.resultHash), - challenger: Address.from(event.args!.challenger), + challenger: EthereumAddress.from(event.args!.challenger), reason: event.args!.reason, } }) diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index 1d0da98f3..00ea5c0ef 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -2,7 +2,7 @@ import { BigNumber } from "ethers" import { BitcoinRawTx, BitcoinUtxo, BitcoinTxHash } from "../../src/lib/bitcoin" import { Deposit } from "../../src/lib/contracts" import { calculateDepositRefundLocktime } from "../../src/deposit" -import { Address } from "../../src/lib/ethereum" +import { EthereumAddress } from "../../src/lib/ethereum" /** * Testnet private key that can be used to refund the deposits used in tests. @@ -52,7 +52,9 @@ export const depositRefundOfWitnessDepositAndWitnessRefunderAddress: DepositRefu "00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(100000), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", @@ -105,7 +107,9 @@ export const depositRefundOfNonWitnessDepositAndWitnessRefunderAddress: DepositR "2eb90b4af908db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(90000), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", @@ -158,7 +162,9 @@ export const depositRefundOfWitnessDepositAndNonWitnessRefunderAddress: DepositR "d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(150000), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index 18e96b513..abaf1d7dd 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -10,7 +10,7 @@ import { import { Deposit } from "../../src/lib/contracts" import { calculateDepositRefundLocktime } from "../../src/deposit" import { BigNumber } from "ethers" -import { Address } from "../../src/lib/ethereum" +import { EthereumAddress } from "../../src/lib/ethereum" import { Hex } from "../../src" export const NO_MAIN_UTXO = { @@ -61,7 +61,9 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = "db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(25000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", @@ -88,7 +90,9 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = "8d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(12000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. @@ -147,7 +151,9 @@ export const depositSweepWithNoMainUtxoAndNonWitnessOutput: DepositSweepTestData "01000000000101dcd1de7b256376f1e05b3c20846868401aee2a85c27990b95886e0d2970a3fc40100000000ffffffff02983a00000000000017a914a9a5f97d5d3c4687a52e90718168270005b369c487f065120000000000160014e257eccafbc07c381642ce6e7e55120fb077fbed02483045022100baccb37cb46a20d79ccd3875162ab8b614a671cc64dc37d3477e24ef5eb61d7102204c68c5a5caff7e5089c1cacaa173fb5aad9529642773501b5e8d88abe7b4fc9c0121039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(15000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", @@ -198,7 +204,9 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa "08db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(17000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", @@ -226,7 +234,9 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa "8d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(10000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", @@ -320,7 +330,9 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes "e7bc11907bfa00000000", }, data: { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), amount: BigNumber.from(19000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index 8cefbc42d..c66863c50 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -10,7 +10,7 @@ import { BitcoinAddressConverter, } from "../../src/lib/bitcoin" import { RedemptionRequest, WalletState } from "../../src/lib/contracts" -import { Address } from "../../src/lib/ethereum" +import { EthereumAddress } from "../../src/lib/ethereum" import { Hex } from "../../src" /** @@ -78,7 +78,9 @@ export const singleP2PKHRedemptionWithWitnessChange: RedemptionTestData = { redemptionKey: "0xcb493004c645792101cfa4cc5da4c16aa3148065034371a6f1478b7df4b92d39", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2PKH address mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5 redeemerOutputScript: "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac", @@ -133,7 +135,9 @@ export const singleP2WPKHRedemptionWithWitnessChange: RedemptionTestData = { redemptionKey: "0x52a5e94b7f933cbc9565c61d43a83921a6b7bbf950156a2dfda7743a7cefffbf", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2WPKH address tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l redeemerOutputScript: "00144130879211c54df460e484ddf9aac009cb38ee74", requestedAmount: BigNumber.from(15000), @@ -187,7 +191,9 @@ export const singleP2SHRedemptionWithWitnessChange: RedemptionTestData = { redemptionKey: "0x4f5c364239f365622168b8fcb3f4556a8bbad22f5b5ae598757c4fe83b3a78d7", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2SH address 2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C redeemerOutputScript: "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87", requestedAmount: BigNumber.from(13000), @@ -241,7 +247,9 @@ export const singleP2WSHRedemptionWithWitnessChange: RedemptionTestData = { redemptionKey: "0x2636de6d29da2c7e229a31f3a39b151e2dcd149b1cc2c4e28008f9ab1b02c112", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2WSH address tb1qs63s8nwjut4tr5t8nudgzwp4m3dpkefjzpmumn90pruce0cye2tq2jkq0y redeemerOutputScript: "002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96", @@ -296,7 +304,9 @@ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { redemptionKey: "0xcb493004c645792101cfa4cc5da4c16aa3148065034371a6f1478b7df4b92d39", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2PKH address mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5 redeemerOutputScript: "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac", @@ -310,7 +320,9 @@ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { redemptionKey: "0x52a5e94b7f933cbc9565c61d43a83921a6b7bbf950156a2dfda7743a7cefffbf", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2WPKH address tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l redeemerOutputScript: "00144130879211c54df460e484ddf9aac009cb38ee74", requestedAmount: BigNumber.from(13000), @@ -323,7 +335,9 @@ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { redemptionKey: "0x4f5c364239f365622168b8fcb3f4556a8bbad22f5b5ae598757c4fe83b3a78d7", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2SH address 2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C redeemerOutputScript: "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87", requestedAmount: BigNumber.from(12000), @@ -336,7 +350,9 @@ export const multipleRedemptionsWithWitnessChange: RedemptionTestData = { redemptionKey: "0x2636de6d29da2c7e229a31f3a39b151e2dcd149b1cc2c4e28008f9ab1b02c112", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2WSH address tb1qs63s8nwjut4tr5t8nudgzwp4m3dpkefjzpmumn90pruce0cye2tq2jkq0y redeemerOutputScript: "002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96", @@ -394,7 +410,9 @@ export const multipleRedemptionsWithoutChange: RedemptionTestData = { redemptionKey: "0xcb493004c645792101cfa4cc5da4c16aa3148065034371a6f1478b7df4b92d39", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2PKH address mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5 redeemerOutputScript: "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac", @@ -408,7 +426,9 @@ export const multipleRedemptionsWithoutChange: RedemptionTestData = { redemptionKey: "0xa690d9da3e64c337eb11344b94cf948ec2da333f0a985e09f1c120a326f6de87", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2WPKH address tb1qf0ulldawp79s7knz9v254j5zjyn0demfx2d0xx redeemerOutputScript: "00144bf9ffb7ae0f8b0f5a622b154aca829126f6e769", requestedAmount: BigNumber.from(4000), @@ -465,7 +485,9 @@ export const singleP2SHRedemptionWithNonWitnessChange: RedemptionTestData = { redemptionKey: "0x4f5c364239f365622168b8fcb3f4556a8bbad22f5b5ae598757c4fe83b3a78d7", pendingRedemption: { - redeemer: Address.from("82883a4c7a8dd73ef165deb402d432613615ced4"), + redeemer: EthereumAddress.from( + "82883a4c7a8dd73ef165deb402d432613615ced4" + ), // script for testnet P2SH address 2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C redeemerOutputScript: "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87", requestedAmount: BigNumber.from(12000), @@ -872,7 +894,9 @@ export const findWalletForRedemptionData: { }, }, pendingRedemption: { - redeemer: Address.from("0xeb9af8E66869902476347a4eFe59a527a57240ED"), + redeemer: EthereumAddress.from( + "0xeb9af8E66869902476347a4eFe59a527a57240ED" + ), // script for testnet P2PKH address mjc2zGWypwpNyDi4ZxGbBNnUA84bfgiwYc redeemerOutputScript: "76a9142cd680318747b720d67bf4246eb7403b476adb3488ac", requestedAmount: BigNumber.from(1000000), diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index f002c570d..c8cac6779 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -32,7 +32,7 @@ import { suggestDepositWallet, } from "../src/deposit" import { MockBridge } from "./utils/mock-bridge" -import { Address } from "../src/lib/ethereum" +import { EthereumAddress } from "../src/lib/ethereum" import { BitcoinNetwork } from "../src" describe("Deposit", () => { @@ -40,7 +40,7 @@ describe("Deposit", () => { const depositRefundLocktimeDuration: number = 2592000 const deposit: Deposit = { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), amount: BigNumber.from(10000), // 0.0001 BTC // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", diff --git a/typescript/test/ethereum.test.ts b/typescript/test/ethereum.test.ts index 363202e9b..1a9c9f1ba 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/ethereum.test.ts @@ -1,4 +1,8 @@ -import { Address, Bridge, TBTCToken } from "../src/lib/ethereum" +import { + EthereumAddress, + EthereumBridge, + EthereumTBTCToken, +} from "../src/lib/ethereum" import { deployMockContract, MockContract, @@ -19,7 +23,7 @@ describe("Ethereum", () => { describe("Bridge", () => { let walletRegistry: MockContract let bridgeContract: MockContract - let bridgeHandle: Bridge + let bridgeHandle: EthereumBridge beforeEach(async () => { const [signer] = new MockProvider().getWallets() @@ -41,7 +45,7 @@ describe("Ethereum", () => { constants.AddressZero ) - bridgeHandle = new Bridge({ + bridgeHandle = new EthereumBridge({ address: bridgeContract.address, signerOrProvider: signer, }) @@ -73,7 +77,9 @@ describe("Ethereum", () => { "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" ) ).to.be.eql({ - redeemer: Address.from("f39fd6e51aad88f6f4ce6ab8827279cfffb92266"), + redeemer: EthereumAddress.from( + "f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ), redeemerOutputScript: "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87", requestedAmount: BigNumber.from(10000), @@ -110,7 +116,9 @@ describe("Ethereum", () => { "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" ) ).to.be.eql({ - redeemer: Address.from("f39fd6e51aad88f6f4ce6ab8827279cfffb92266"), + redeemer: EthereumAddress.from( + "f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ), redeemerOutputScript: "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87", requestedAmount: BigNumber.from(10000), @@ -135,13 +143,15 @@ describe("Ethereum", () => { }, 2, { - depositor: Address.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), + depositor: EthereumAddress.from( + "934b98637ca318a4d6e7ca6ffd1690b8e77df637" + ), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "28e081f285138ccbe389c1eb8985716230129f89", blindingFactor: "f9f0c90d00039523", refundLocktime: "60bcea61", }, - Address.from("82883a4c7a8dd73ef165deb402d432613615ced4") + EthereumAddress.from("82883a4c7a8dd73ef165deb402d432613615ced4") ) }) @@ -188,7 +198,7 @@ describe("Ethereum", () => { outputIndex: 8, value: BigNumber.from(9999), }, - Address.from("82883a4c7a8dd73ef165deb402d432613615ced4") + EthereumAddress.from("82883a4c7a8dd73ef165deb402d432613615ced4") ) }) @@ -342,9 +352,13 @@ describe("Ethereum", () => { 0 ) ).to.be.eql({ - depositor: Address.from("f39fd6e51aad88f6f4ce6ab8827279cfffb92266"), + depositor: EthereumAddress.from( + "f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ), amount: BigNumber.from(10000), - vault: Address.from("014e1bfbe0f85f129749a8ae0fcb20175433741b"), + vault: EthereumAddress.from( + "014e1bfbe0f85f129749a8ae0fcb20175433741b" + ), revealedAt: 1654774330, sweptAt: 1655033516, treasuryFee: BigNumber.from(200), @@ -381,7 +395,9 @@ describe("Ethereum", () => { 0 ) ).to.be.eql({ - depositor: Address.from("f39fd6e51aad88f6f4ce6ab8827279cfffb92266"), + depositor: EthereumAddress.from( + "f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + ), amount: BigNumber.from(10000), vault: undefined, revealedAt: 1654774330, @@ -475,7 +491,7 @@ describe("Ethereum", () => { describe("TBTCToken", () => { let tbtcToken: MockContract - let tokenHandle: TBTCToken + let tokenHandle: EthereumTBTCToken const signer: Wallet = new MockProvider().getWallets()[0] beforeEach(async () => { @@ -484,7 +500,7 @@ describe("Ethereum", () => { `${JSON.stringify(TBTCTokenABI)}` ) - tokenHandle = new TBTCToken({ + tokenHandle = new EthereumTBTCToken({ address: tbtcToken.address, signerOrProvider: signer, }) @@ -492,7 +508,9 @@ describe("Ethereum", () => { describe("requestRedemption", () => { const data = { - vault: Address.from("0x24BE35e7C04E2e0a628614Ce0Ed58805e1C894F7"), + vault: EthereumAddress.from( + "0x24BE35e7C04E2e0a628614Ce0Ed58805e1C894F7" + ), walletPublicKey: "03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9", mainUtxo: { @@ -502,7 +520,7 @@ describe("Ethereum", () => { outputIndex: 8, value: BigNumber.from(9999), }, - redeemer: Address.from(signer.address), + redeemer: EthereumAddress.from(signer.address), amount: BigNumber.from(10000), redeemerOutputScript: { unprefixed: diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index 7981adc7c..481234a82 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -20,7 +20,7 @@ import { } from "../../src/lib/bitcoin" import { BigNumberish, BigNumber, utils, constants } from "ethers" import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" -import { Address } from "../../src/lib/ethereum" +import { EthereumAddress } from "../../src/lib/ethereum" import { Hex } from "../../src/lib/utils" interface DepositSweepProofLogEntry { @@ -148,7 +148,7 @@ export class MockBridge implements Bridge { walletPublicKeyHash: deposit.data.walletPublicKeyHash, refundPublicKeyHash: deposit.data.refundPublicKeyHash, refundLocktime: deposit.data.refundLocktime, - vault: new Address(constants.AddressZero), + vault: EthereumAddress.from(constants.AddressZero), }, ]) }) @@ -194,9 +194,9 @@ export class MockBridge implements Bridge { this._deposits.has(depositKey) ? (this._deposits.get(depositKey) as RevealedDeposit) : { - depositor: Address.from(constants.AddressZero), + depositor: EthereumAddress.from(constants.AddressZero), amount: BigNumber.from(0), - vault: Address.from(constants.AddressZero), + vault: EthereumAddress.from(constants.AddressZero), revealedAt: 0, sweptAt: 0, treasuryFee: BigNumber.from(0), @@ -304,7 +304,7 @@ export class MockBridge implements Bridge { return redemptionsMap.has(redemptionKey) ? (redemptionsMap.get(redemptionKey) as RedemptionRequest) : { - redeemer: Address.from(constants.AddressZero), + redeemer: EthereumAddress.from(constants.AddressZero), redeemerOutputScript: "", requestedAmount: BigNumber.from(0), treasuryFee: BigNumber.from(0), From 2016d5e3aafbd8a6ce104d3b660b6e152cc5b126 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 26 Sep 2023 13:58:03 +0200 Subject: [PATCH 15/43] Rework deposit domain objects Replace current objects with `DepositReceipt` carrying state relevant for Bitcoin and `DepositRequest` carrying state relevant for the Bridge contract. --- typescript/src/deposit-refund.ts | 42 ++++-------------- typescript/src/deposit-sweep.ts | 28 +++++------- typescript/src/deposit.ts | 40 ++++++++++------- typescript/src/lib/contracts/bridge.ts | 61 +++++++++++--------------- typescript/src/lib/ethereum/bridge.ts | 20 ++++----- typescript/test/data/deposit-refund.ts | 7 +-- typescript/test/data/deposit-sweep.ts | 11 +---- typescript/test/deposit-sweep.test.ts | 25 ----------- typescript/test/deposit.test.ts | 43 ++++++------------ typescript/test/utils/mock-bridge.ts | 18 ++++---- 10 files changed, 103 insertions(+), 192 deletions(-) diff --git a/typescript/src/deposit-refund.ts b/typescript/src/deposit-refund.ts index d2fe47da8..0cf2bac52 100644 --- a/typescript/src/deposit-refund.ts +++ b/typescript/src/deposit-refund.ts @@ -9,11 +9,8 @@ import { BitcoinHashUtils, BitcoinPublicKeyUtils, } from "./lib/bitcoin" -import { Deposit } from "./lib/contracts" -import { - assembleDepositScript, - validateDepositScriptParameters, -} from "./deposit" +import { assembleDepositScript, validateDepositReceipt } from "./deposit" +import { DepositReceipt } from "./lib/contracts" /** * Submits a deposit refund by creating and broadcasting a Bitcoin P2(W)PKH @@ -39,7 +36,7 @@ export async function submitDepositRefundTransaction( bitcoinClient: BitcoinClient, fee: BigNumber, utxo: BitcoinUtxo, - deposit: Deposit, + deposit: DepositReceipt, refunderAddress: string, refunderPrivateKey: string ): Promise<{ transactionHash: BitcoinTxHash }> { @@ -87,14 +84,14 @@ export async function submitDepositRefundTransaction( export async function assembleDepositRefundTransaction( fee: BigNumber, utxo: BitcoinUtxo & BitcoinRawTx, - deposit: Deposit, + deposit: DepositReceipt, refunderAddress: string, refunderPrivateKey: string ): Promise<{ transactionHash: BitcoinTxHash rawTransaction: BitcoinRawTx }> { - validateInputParameters(deposit, utxo) + validateDepositReceipt(deposit) const refunderKeyRing = BitcoinPrivateKeyUtils.createKeyRing(refunderPrivateKey) @@ -171,7 +168,7 @@ export async function assembleDepositRefundTransaction( async function prepareInputSignData( transaction: any, inputIndex: number, - deposit: Deposit, + deposit: DepositReceipt, refunderKeyRing: any ): Promise<{ refunderPublicKey: string @@ -181,10 +178,6 @@ async function prepareInputSignData( const previousOutpoint = transaction.inputs[inputIndex].prevout const previousOutput = transaction.view.getOutput(previousOutpoint) - if (previousOutput.value != deposit.amount.toNumber()) { - throw new Error("Mismatch between amount in deposit and deposit refund tx") - } - const refunderPublicKey = refunderKeyRing.getPublicKey("hex") if ( BitcoinHashUtils.computeHash160(refunderKeyRing.getPublicKey("hex")) != @@ -199,11 +192,8 @@ async function prepareInputSignData( throw new Error("Refunder public key must be compressed") } - // eslint-disable-next-line no-unused-vars - const { amount, vault, ...depositScriptParameters } = deposit - const depositScript = bcoin.Script.fromRaw( - Buffer.from(await assembleDepositScript(depositScriptParameters), "hex") + Buffer.from(await assembleDepositScript(deposit), "hex") ) return { @@ -225,7 +215,7 @@ async function prepareInputSignData( async function signP2SHDepositInput( transaction: any, inputIndex: number, - deposit: Deposit, + deposit: DepositReceipt, refunderKeyRing: any ) { const { refunderPublicKey, depositScript, previousOutputValue } = @@ -266,7 +256,7 @@ async function signP2SHDepositInput( async function signP2WSHDepositInput( transaction: any, inputIndex: number, - deposit: Deposit, + deposit: DepositReceipt, refunderKeyRing: any ) { const { refunderPublicKey, depositScript, previousOutputValue } = @@ -309,17 +299,3 @@ function locktimeToUnixTimestamp(locktime: string): number { return parseInt(bigEndianLocktime, 16) } - -/** - * Validates whether the provided input parameters are correct. - * @param deposit - Data of the deposit to be refunded. - * @param utxo - UTXO that was created during depositing that needs be refunded. - * @returns Empty return. - */ -function validateInputParameters(deposit: Deposit, utxo: BitcoinUtxo) { - validateDepositScriptParameters(deposit) - - if (!deposit.amount.eq(utxo.value)) { - throw new Error("Mismatch between provided deposit amount and utxo value") - } -} diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index 2961ece02..cfe828a69 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -12,7 +12,7 @@ import { BitcoinHashUtils, } from "./lib/bitcoin" import { assembleDepositScript } from "./deposit" -import { Bridge, ChainIdentifier, Deposit } from "./lib/contracts" +import { Bridge, ChainIdentifier, DepositReceipt } from "./lib/contracts" /** * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and @@ -42,7 +42,7 @@ export async function submitDepositSweepTransaction( walletPrivateKey: string, witness: boolean, utxos: BitcoinUtxo[], - deposits: Deposit[], + deposits: DepositReceipt[], mainUtxo?: BitcoinUtxo ): Promise<{ transactionHash: BitcoinTxHash @@ -115,7 +115,7 @@ export async function assembleDepositSweepTransaction( walletPrivateKey: string, witness: boolean, utxos: (BitcoinUtxo & BitcoinRawTx)[], - deposits: Deposit[], + deposits: DepositReceipt[], mainUtxo?: BitcoinUtxo & BitcoinRawTx ): Promise<{ transactionHash: BitcoinTxHash @@ -180,12 +180,11 @@ export async function assembleDepositSweepTransaction( // UTXOs must be mapped to deposits, as `fund` may arrange inputs in any // order - const utxosWithDeposits: (BitcoinUtxo & BitcoinRawTx & Deposit)[] = utxos.map( - (utxo, index) => ({ + const utxosWithDeposits: (BitcoinUtxo & BitcoinRawTx & DepositReceipt)[] = + utxos.map((utxo, index) => ({ ...utxo, ...deposits[index], - }) - ) + })) for (let i = 0; i < transaction.inputs.length; i++) { const previousOutpoint = transaction.inputs[i].prevout @@ -274,7 +273,7 @@ async function signMainUtxoInput( async function signP2SHDepositInput( transaction: any, inputIndex: number, - deposit: Deposit, + deposit: DepositReceipt, walletKeyRing: any ): Promise { const { walletPublicKey, depositScript, previousOutputValue } = @@ -310,7 +309,7 @@ async function signP2SHDepositInput( async function signP2WSHDepositInput( transaction: any, inputIndex: number, - deposit: Deposit, + deposit: DepositReceipt, walletKeyRing: any ): Promise { const { walletPublicKey, depositScript, previousOutputValue } = @@ -346,7 +345,7 @@ async function signP2WSHDepositInput( async function prepareInputSignData( transaction: any, inputIndex: number, - deposit: Deposit, + deposit: DepositReceipt, walletKeyRing: any ): Promise<{ walletPublicKey: string @@ -356,10 +355,6 @@ async function prepareInputSignData( const previousOutpoint = transaction.inputs[inputIndex].prevout const previousOutput = transaction.view.getOutput(previousOutpoint) - if (previousOutput.value != deposit.amount.toNumber()) { - throw new Error("Mismatch between amount in deposit and deposit tx") - } - const walletPublicKey = walletKeyRing.getPublicKey("hex") if ( BitcoinHashUtils.computeHash160(walletKeyRing.getPublicKey("hex")) != @@ -374,11 +369,8 @@ async function prepareInputSignData( throw new Error("Wallet public key must be compressed") } - // eslint-disable-next-line no-unused-vars - const { amount, vault, ...depositScriptParameters } = deposit - const depositScript = bcoin.Script.fromRaw( - Buffer.from(await assembleDepositScript(depositScriptParameters), "hex") + Buffer.from(await assembleDepositScript(deposit), "hex") ) return { diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts index 4d4684998..78b506dd9 100644 --- a/typescript/src/deposit.ts +++ b/typescript/src/deposit.ts @@ -13,14 +13,23 @@ import { import { Bridge, ChainIdentifier, - Deposit, - DepositScriptParameters, - RevealedDeposit, + DepositReceipt, + DepositRequest, } from "./lib/contracts" import { Hex } from "./lib/utils" const { opcodes } = bcoin.script.common +/** + * Deposit receipt enhanced with deposit amount information. + */ +export type DepositReceiptWithAmount = DepositReceipt & { + /** + * Deposit amount in satoshis. + */ + amount: BigNumber +} + /** * Submits a deposit by creating and broadcasting a Bitcoin P2(W)SH * deposit transaction. @@ -34,7 +43,7 @@ const { opcodes } = bcoin.script.common * - the deposit UTXO produced by this transaction. */ export async function submitDepositTransaction( - deposit: Deposit, + deposit: DepositReceiptWithAmount, depositorPrivateKey: string, bitcoinClient: BitcoinClient, witness: boolean @@ -91,7 +100,7 @@ export async function submitDepositTransaction( * - the deposit transaction in the raw format */ export async function assembleDepositTransaction( - deposit: Deposit, + deposit: DepositReceiptWithAmount, utxos: (BitcoinUtxo & BitcoinRawTx)[], depositorPrivateKey: string, witness: boolean @@ -152,9 +161,9 @@ export async function assembleDepositTransaction( * @returns Script as an un-prefixed hex string. */ export async function assembleDepositScript( - deposit: DepositScriptParameters + deposit: DepositReceipt ): Promise { - validateDepositScriptParameters(deposit) + validateDepositReceipt(deposit) // All HEXes pushed to the script must be un-prefixed. const script = new bcoin.Script() @@ -187,15 +196,12 @@ export async function assembleDepositScript( // eslint-disable-next-line valid-jsdoc /** - * Validates the given deposit script parameters. Throws in case of a - * validation error. - * @param deposit - The validated deposit script parameters. + * Validates the given deposit receipt. Throws in case of a validation error. + * @param deposit - The validated deposit receipt. * @dev This function does not validate the depositor's identifier as its * validity is chain-specific. This parameter must be validated outside. */ -export function validateDepositScriptParameters( - deposit: DepositScriptParameters -) { +export function validateDepositReceipt(deposit: DepositReceipt) { if (deposit.blindingFactor.length != 16) { throw new Error("Blinding factor must be an 8-byte number") } @@ -250,7 +256,7 @@ export function calculateDepositRefundLocktime( * @returns Buffer with script hash. */ export async function calculateDepositScriptHash( - deposit: DepositScriptParameters, + deposit: DepositReceipt, witness: boolean ): Promise { const script = await assembleDepositScript(deposit) @@ -270,7 +276,7 @@ export async function calculateDepositScriptHash( * @returns Address as string. */ export async function calculateDepositAddress( - deposit: DepositScriptParameters, + deposit: DepositReceipt, network: BitcoinNetwork, witness: boolean ): Promise { @@ -295,7 +301,7 @@ export async function calculateDepositAddress( */ export async function revealDeposit( utxo: BitcoinUtxo, - deposit: DepositScriptParameters, + deposit: DepositReceipt, bitcoinClient: BitcoinClient, bridge: Bridge, vault?: ChainIdentifier @@ -316,7 +322,7 @@ export async function revealDeposit( export async function getRevealedDeposit( utxo: BitcoinUtxo, bridge: Bridge -): Promise { +): Promise { return bridge.deposits(utxo.transactionHash, utxo.outputIndex) } diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index eaea4f64b..503acf77b 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -48,7 +48,7 @@ export interface Bridge { revealDeposit( depositTx: BitcoinRawTxVectors, depositOutputIndex: number, - deposit: DepositScriptParameters, + deposit: DepositReceipt, vault?: ChainIdentifier ): Promise // TODO: Update to Hex @@ -62,7 +62,7 @@ export interface Bridge { deposits( depositTxHash: BitcoinTxHash, depositOutputIndex: number - ): Promise + ): Promise /** * Requests a redemption from the on-chain contract. @@ -180,19 +180,15 @@ export interface Bridge { // strings with a Hex type. /** - * Represents a deposit. + * Represents a deposit receipt. The receipt holds all information required + * to build a unique deposit address on Bitcoin chain. */ -export interface Deposit { +export interface DepositReceipt { /** * Depositor's chain identifier. */ depositor: ChainIdentifier - /** - * Deposit amount in satoshis. - */ - amount: BigNumber - /** * An 8-byte blinding factor as an un-prefixed hex string. Must be unique * for the given depositor, wallet public key and refund public key. @@ -219,35 +215,27 @@ export interface Deposit { * A 4-byte little-endian refund locktime as an un-prefixed hex string. */ refundLocktime: string +} + +/** + * Represents a deposit request revealed to the on-chain bridge. + */ +export interface DepositRequest { + /** + * Depositor's chain identifier. + */ + depositor: ChainIdentifier + + /** + * Deposit amount in satoshis. + */ + amount: BigNumber /** * Optional identifier of the vault the deposit should be routed in. */ vault?: ChainIdentifier -} -/** - * Helper type that groups deposit's fields required to assemble a deposit - * script. - */ -export type DepositScriptParameters = Pick< - Deposit, - | "depositor" - | "blindingFactor" - | "refundLocktime" - | "walletPublicKeyHash" - | "refundPublicKeyHash" -> & {} - -/** - * Represents a deposit revealed to the on-chain bridge. This type emphasizes - * the on-chain state of the revealed deposit and omits the deposit script - * parameters as they are not relevant in this context. - */ -export type RevealedDeposit = Pick< - Deposit, - "depositor" | "amount" | "vault" -> & { /** * UNIX timestamp the deposit was revealed at. */ @@ -267,10 +255,11 @@ export type RevealedDeposit = Pick< /** * Represents an event emitted on deposit reveal to the on-chain bridge. */ -export type DepositRevealedEvent = Deposit & { - fundingTxHash: BitcoinTxHash - fundingOutputIndex: number -} & ChainEvent +export type DepositRevealedEvent = DepositReceipt & + Pick & { + fundingTxHash: BitcoinTxHash + fundingOutputIndex: number + } & ChainEvent /** * Represents a redemption request. diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index 1d424dd1b..fda724628 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -15,8 +15,8 @@ import { RedemptionRequest, RedemptionRequestedEvent, DepositRevealedEvent, - DepositScriptParameters, - RevealedDeposit, + DepositReceipt, + DepositRequest, } from "../contracts" import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber, constants, ContractTransaction, utils } from "ethers" @@ -200,7 +200,7 @@ export class EthereumBridge async revealDeposit( depositTx: BitcoinRawTxVectors, depositOutputIndex: number, - deposit: DepositScriptParameters, + deposit: DepositReceipt, vault?: ChainIdentifier ): Promise { const depositTxParam = { @@ -391,7 +391,7 @@ export class EthereumBridge async deposits( depositTxHash: BitcoinTxHash, depositOutputIndex: number - ): Promise { + ): Promise { const depositKey = EthereumBridge.buildDepositKey( depositTxHash, depositOutputIndex @@ -404,7 +404,7 @@ export class EthereumBridge } ) - return this.parseRevealedDeposit(deposit) + return this.parseDepositRequest(deposit) } /** @@ -429,13 +429,13 @@ export class EthereumBridge } /** - * Parses a revealed deposit using data fetched from the on-chain contract. - * @param deposit Data of the revealed deposit. - * @returns Parsed revealed deposit. + * Parses a deposit request using data fetched from the on-chain contract. + * @param deposit Data of the deposit request. + * @returns Parsed deposit request. */ - private parseRevealedDeposit( + private parseDepositRequest( deposit: DepositRequestTypechain - ): RevealedDeposit { + ): DepositRequest { return { depositor: EthereumAddress.from(deposit.depositor), amount: BigNumber.from(deposit.amount), diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index 00ea5c0ef..4edde113c 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -1,6 +1,6 @@ import { BigNumber } from "ethers" import { BitcoinRawTx, BitcoinUtxo, BitcoinTxHash } from "../../src/lib/bitcoin" -import { Deposit } from "../../src/lib/contracts" +import { DepositReceipt } from "../../src/lib/contracts" import { calculateDepositRefundLocktime } from "../../src/deposit" import { EthereumAddress } from "../../src/lib/ethereum" @@ -18,7 +18,7 @@ export const refunderPrivateKey = export interface DepositRefundTestData { deposit: { utxo: BitcoinUtxo & BitcoinRawTx - data: Deposit + data: DepositReceipt } refunderAddress: string expectedRefund: { @@ -55,7 +55,6 @@ export const depositRefundOfWitnessDepositAndWitnessRefunderAddress: DepositRefu depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(100000), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", blindingFactor: "f9f0c90d00039523", @@ -110,7 +109,6 @@ export const depositRefundOfNonWitnessDepositAndWitnessRefunderAddress: DepositR depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(90000), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", blindingFactor: "f9f0c90d00039523", @@ -165,7 +163,6 @@ export const depositRefundOfWitnessDepositAndNonWitnessRefunderAddress: DepositR depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(150000), walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", blindingFactor: "f9f0c90d00039523", diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index abaf1d7dd..fd85dcd64 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -7,7 +7,7 @@ import { BitcoinTxMerkleBranch, BitcoinTxHash, } from "../../src/lib/bitcoin" -import { Deposit } from "../../src/lib/contracts" +import { DepositReceipt } from "../../src/lib/contracts" import { calculateDepositRefundLocktime } from "../../src/deposit" import { BigNumber } from "ethers" import { EthereumAddress } from "../../src/lib/ethereum" @@ -26,7 +26,7 @@ export const NO_MAIN_UTXO = { export interface DepositSweepTestData { deposits: { utxo: BitcoinUtxo & BitcoinRawTx - data: Deposit + data: DepositReceipt }[] mainUtxo: BitcoinUtxo & BitcoinRawTx witness: boolean @@ -64,7 +64,6 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(25000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. @@ -93,8 +92,6 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - - amount: BigNumber.from(12000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. @@ -154,7 +151,6 @@ export const depositSweepWithNoMainUtxoAndNonWitnessOutput: DepositSweepTestData depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(15000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. @@ -207,7 +203,6 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(17000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. @@ -237,7 +232,6 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(10000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. @@ -333,7 +327,6 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes depositor: EthereumAddress.from( "934b98637ca318a4d6e7ca6ffd1690b8e77df637" ), - amount: BigNumber.from(19000), // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts index c23e0835d..b92c63b84 100644 --- a/typescript/test/deposit-sweep.test.ts +++ b/typescript/test/deposit-sweep.test.ts @@ -919,31 +919,6 @@ describe("Sweep", () => { } ) - context( - "when there is a mismatch between the UTXO's value and amount in deposit", - () => { - const utxoWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - // Use a deposit that does not match the UTXO - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].data - - it("should revert", async () => { - await expect( - assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - true, - [utxoWithRaw], - [deposit] - ) - ).to.be.rejectedWith( - "Mismatch between amount in deposit and deposit tx" - ) - }) - } - ) - context("when the main UTXO does not belong to the wallet", () => { const utxoWithRaw = depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index c8cac6779..5d327fe58 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -13,11 +13,7 @@ import { BitcoinTxHash, BitcoinUtxo, } from "../src/lib/bitcoin" -import { - Deposit, - DepositScriptParameters, - RevealedDeposit, -} from "../src/lib/contracts" +import { DepositRequest } from "../src/lib/contracts" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import bcoin from "bcoin" import { @@ -26,6 +22,7 @@ import { calculateDepositAddress, calculateDepositRefundLocktime, calculateDepositScriptHash, + DepositReceiptWithAmount, getRevealedDeposit, revealDeposit, submitDepositTransaction, @@ -39,7 +36,7 @@ describe("Deposit", () => { const depositCreatedAt: number = 1640181600 const depositRefundLocktimeDuration: number = 2592000 - const deposit: Deposit = { + const deposit: DepositReceiptWithAmount = { depositor: EthereumAddress.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), amount: BigNumber.from(10000), // 0.0001 BTC // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. @@ -53,14 +50,6 @@ describe("Deposit", () => { ), } - const depositScriptParameters: DepositScriptParameters = { - depositor: deposit.depositor, - walletPublicKeyHash: deposit.walletPublicKeyHash, - refundPublicKeyHash: deposit.refundPublicKeyHash, - blindingFactor: deposit.blindingFactor, - refundLocktime: deposit.refundLocktime, - } - // All test scenarios using the deposit script within `Deposit` group // expect the same deposit script: const expectedDepositScript = @@ -518,7 +507,7 @@ describe("Deposit", () => { let script: string beforeEach(async () => { - script = await assembleDepositScript(depositScriptParameters) + script = await assembleDepositScript(deposit) }) it("should return script with proper structure", async () => { @@ -567,10 +556,7 @@ describe("Deposit", () => { let scriptHash: Buffer beforeEach(async () => { - scriptHash = await calculateDepositScriptHash( - depositScriptParameters, - true - ) + scriptHash = await calculateDepositScriptHash(deposit, true) }) it("should return proper witness script hash", async () => { @@ -591,10 +577,7 @@ describe("Deposit", () => { let scriptHash: Buffer beforeEach(async () => { - scriptHash = await calculateDepositScriptHash( - depositScriptParameters, - false - ) + scriptHash = await calculateDepositScriptHash(deposit, false) }) it("should return proper non-witness script hash", async () => { @@ -619,7 +602,7 @@ describe("Deposit", () => { context("when witness option is true", () => { beforeEach(async () => { address = await calculateDepositAddress( - depositScriptParameters, + deposit, BitcoinNetwork.Mainnet, true ) @@ -637,7 +620,7 @@ describe("Deposit", () => { context("when witness option is false", () => { beforeEach(async () => { address = await calculateDepositAddress( - depositScriptParameters, + deposit, BitcoinNetwork.Mainnet, false ) @@ -657,7 +640,7 @@ describe("Deposit", () => { context("when witness option is true", () => { beforeEach(async () => { address = await calculateDepositAddress( - depositScriptParameters, + deposit, BitcoinNetwork.Testnet, true ) @@ -675,7 +658,7 @@ describe("Deposit", () => { context("when witness option is false", () => { beforeEach(async () => { address = await calculateDepositAddress( - depositScriptParameters, + deposit, BitcoinNetwork.Testnet, false ) @@ -737,7 +720,7 @@ describe("Deposit", () => { describe("getRevealedDeposit", () => { let depositUtxo: BitcoinUtxo - let revealedDeposit: RevealedDeposit + let revealedDeposit: DepositRequest let bridge: MockBridge beforeEach(async () => { @@ -752,13 +735,13 @@ describe("Deposit", () => { revealedDeposit = { depositor: deposit.depositor, amount: deposit.amount, - vault: deposit.vault, + vault: EthereumAddress.from("954b98637ca318a4d6e7ca6ffd1690b8e77df637"), revealedAt: 1654774330, sweptAt: 1655033516, treasuryFee: BigNumber.from(200), } - const revealedDeposits = new Map() + const revealedDeposits = new Map() revealedDeposits.set( MockBridge.buildDepositKey( depositUtxo.transactionHash, diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index 481234a82..8e7ecc118 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -7,9 +7,9 @@ import { Wallet, RedemptionRequest, RedemptionRequestedEvent, - Deposit, + DepositReceipt, DepositRevealedEvent, - RevealedDeposit, + DepositRequest, } from "../../src/lib/contracts" import { BitcoinRawTxVectors, @@ -32,7 +32,7 @@ interface DepositSweepProofLogEntry { interface RevealDepositLogEntry { depositTx: BitcoinRawTxVectors depositOutputIndex: number - deposit: Deposit + deposit: DepositReceipt } interface RequestRedemptionLogEntry { @@ -69,7 +69,7 @@ export class MockBridge implements Bridge { private _revealDepositLog: RevealDepositLogEntry[] = [] private _requestRedemptionLog: RequestRedemptionLogEntry[] = [] private _redemptionProofLog: RedemptionProofLogEntry[] = [] - private _deposits = new Map() + private _deposits = new Map() private _activeWalletPublicKey: string | undefined private _newWalletRegisteredEvents: NewWalletRegisteredEvent[] = [] private _newWalletRegisteredEventsLog: NewWalletRegisteredEventsLog[] = [] @@ -116,7 +116,7 @@ export class MockBridge implements Bridge { return this._walletsLog } - setDeposits(value: Map) { + setDeposits(value: Map) { this._deposits = value } @@ -169,7 +169,7 @@ export class MockBridge implements Bridge { revealDeposit( depositTx: BitcoinRawTxVectors, depositOutputIndex: number, - deposit: Deposit + deposit: DepositReceipt ): Promise { this._revealDepositLog.push({ depositTx, depositOutputIndex, deposit }) return new Promise((resolve, _) => { @@ -183,8 +183,8 @@ export class MockBridge implements Bridge { deposits( depositTxHash: BitcoinTxHash, depositOutputIndex: number - ): Promise { - return new Promise((resolve, _) => { + ): Promise { + return new Promise((resolve, _) => { const depositKey = MockBridge.buildDepositKey( depositTxHash, depositOutputIndex @@ -192,7 +192,7 @@ export class MockBridge implements Bridge { resolve( this._deposits.has(depositKey) - ? (this._deposits.get(depositKey) as RevealedDeposit) + ? (this._deposits.get(depositKey) as DepositRequest) : { depositor: EthereumAddress.from(constants.AddressZero), amount: BigNumber.from(0), From dbfe946535090806701ae0f644e4516332f79206 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 28 Sep 2023 15:22:15 +0200 Subject: [PATCH 16/43] Implement the `./services/deposits` feature module Here we remove the existing `deposit.ts` file, move it to the new `./services/deposits` feature module and do a general refactoring to make it easy to use. --- typescript/src/deposit-refund.ts | 6 +- typescript/src/deposit-sweep.ts | 4 +- typescript/src/deposit.ts | 339 ------- typescript/src/index.ts | 11 - typescript/src/lib/bitcoin/tx.ts | 28 + typescript/src/lib/contracts/bridge.ts | 24 + typescript/src/lib/contracts/index.ts | 15 + typescript/src/lib/contracts/tbtc-vault.ts | 5 + typescript/src/lib/ethereum/tbtc-vault.ts | 9 + typescript/src/services/deposits/deposit.ts | 237 +++++ .../src/services/deposits/deposits-service.ts | 97 ++ typescript/src/services/deposits/funding.ts | 155 +++ typescript/src/services/deposits/index.ts | 3 + typescript/test/bitcoin.test.ts | 63 +- typescript/test/data/deposit-refund.ts | 23 +- typescript/test/data/deposit-sweep.ts | 32 +- typescript/test/deposit.test.ts | 914 ++++++++---------- typescript/test/utils/mock-tbtc-contracts.ts | 19 + typescript/test/utils/mock-tbtc-vault.ts | 83 ++ typescript/test/utils/mock-wallet-registry.ts | 35 + 20 files changed, 1229 insertions(+), 873 deletions(-) delete mode 100644 typescript/src/deposit.ts create mode 100644 typescript/src/services/deposits/deposit.ts create mode 100644 typescript/src/services/deposits/deposits-service.ts create mode 100644 typescript/src/services/deposits/funding.ts create mode 100644 typescript/src/services/deposits/index.ts create mode 100644 typescript/test/utils/mock-tbtc-contracts.ts create mode 100644 typescript/test/utils/mock-tbtc-vault.ts create mode 100644 typescript/test/utils/mock-wallet-registry.ts diff --git a/typescript/src/deposit-refund.ts b/typescript/src/deposit-refund.ts index 0cf2bac52..952516e9a 100644 --- a/typescript/src/deposit-refund.ts +++ b/typescript/src/deposit-refund.ts @@ -9,8 +9,8 @@ import { BitcoinHashUtils, BitcoinPublicKeyUtils, } from "./lib/bitcoin" -import { assembleDepositScript, validateDepositReceipt } from "./deposit" -import { DepositReceipt } from "./lib/contracts" +import { DepositReceipt, validateDepositReceipt } from "./lib/contracts" +import { DepositScript } from "./services/deposits" /** * Submits a deposit refund by creating and broadcasting a Bitcoin P2(W)PKH @@ -193,7 +193,7 @@ async function prepareInputSignData( } const depositScript = bcoin.Script.fromRaw( - Buffer.from(await assembleDepositScript(deposit), "hex") + Buffer.from(await DepositScript.fromReceipt(deposit).getPlainText(), "hex") ) return { diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts index cfe828a69..440bfa219 100644 --- a/typescript/src/deposit-sweep.ts +++ b/typescript/src/deposit-sweep.ts @@ -11,8 +11,8 @@ import { BitcoinTxHash, BitcoinHashUtils, } from "./lib/bitcoin" -import { assembleDepositScript } from "./deposit" import { Bridge, ChainIdentifier, DepositReceipt } from "./lib/contracts" +import { DepositScript } from "./services/deposits" /** * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and @@ -370,7 +370,7 @@ async function prepareInputSignData( } const depositScript = bcoin.Script.fromRaw( - Buffer.from(await assembleDepositScript(deposit), "hex") + Buffer.from(await DepositScript.fromReceipt(deposit).getPlainText(), "hex") ) return { diff --git a/typescript/src/deposit.ts b/typescript/src/deposit.ts deleted file mode 100644 index 78b506dd9..000000000 --- a/typescript/src/deposit.ts +++ /dev/null @@ -1,339 +0,0 @@ -import bcoin from "bcoin" -import { BigNumber } from "ethers" -import { - BitcoinClient, - BitcoinNetwork, - toBcoinNetwork, - extractBitcoinRawTxVectors, - BitcoinPrivateKeyUtils, - BitcoinRawTx, - BitcoinUtxo, - BitcoinTxHash, -} from "./lib/bitcoin" -import { - Bridge, - ChainIdentifier, - DepositReceipt, - DepositRequest, -} from "./lib/contracts" -import { Hex } from "./lib/utils" - -const { opcodes } = bcoin.script.common - -/** - * Deposit receipt enhanced with deposit amount information. - */ -export type DepositReceiptWithAmount = DepositReceipt & { - /** - * Deposit amount in satoshis. - */ - amount: BigNumber -} - -/** - * Submits a deposit by creating and broadcasting a Bitcoin P2(W)SH - * deposit transaction. - * @param deposit - Details of the deposit. - * @param depositorPrivateKey - Bitcoin private key of the depositor. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @param witness - If true, a witness (P2WSH) transaction will be created. - * Otherwise, a legacy P2SH transaction will be made. - * @returns The outcome consisting of: - * - the deposit transaction hash, - * - the deposit UTXO produced by this transaction. - */ -export async function submitDepositTransaction( - deposit: DepositReceiptWithAmount, - depositorPrivateKey: string, - bitcoinClient: BitcoinClient, - witness: boolean -): Promise<{ - transactionHash: BitcoinTxHash - depositUtxo: BitcoinUtxo -}> { - const depositorKeyRing = - BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) - const depositorAddress = depositorKeyRing.getAddress("string") - - const utxos = await bitcoinClient.findAllUnspentTransactionOutputs( - depositorAddress - ) - - const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] - for (const utxo of utxos) { - const utxoRawTransaction = await bitcoinClient.getRawTransaction( - utxo.transactionHash - ) - - utxosWithRaw.push({ - ...utxo, - transactionHex: utxoRawTransaction.transactionHex, - }) - } - - const { transactionHash, depositUtxo, rawTransaction } = - await assembleDepositTransaction( - deposit, - utxosWithRaw, - depositorPrivateKey, - witness - ) - - await bitcoinClient.broadcast(rawTransaction) - - return { - transactionHash, - depositUtxo, - } -} - -/** - * Assembles a Bitcoin P2(W)SH deposit transaction. - * @param deposit - Details of the deposit. - * @param utxos - UTXOs that should be used as transaction inputs. - * @param depositorPrivateKey - Bitcoin private key of the depositor. - * @param witness - If true, a witness (P2WSH) transaction will be created. - * Otherwise, a legacy P2SH transaction will be made. - * @returns The outcome consisting of: - * - the deposit transaction hash, - * - the deposit UTXO produced by this transaction. - * - the deposit transaction in the raw format - */ -export async function assembleDepositTransaction( - deposit: DepositReceiptWithAmount, - utxos: (BitcoinUtxo & BitcoinRawTx)[], - depositorPrivateKey: string, - witness: boolean -): Promise<{ - transactionHash: BitcoinTxHash - depositUtxo: BitcoinUtxo - rawTransaction: BitcoinRawTx -}> { - const depositorKeyRing = - BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) - const depositorAddress = depositorKeyRing.getAddress("string") - - const inputCoins = utxos.map((utxo) => - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), - utxo.outputIndex, - -1 - ) - ) - - const transaction = new bcoin.MTX() - - const scriptHash = await calculateDepositScriptHash(deposit, witness) - - transaction.addOutput({ - script: witness - ? bcoin.Script.fromProgram(0, scriptHash) - : bcoin.Script.fromScripthash(scriptHash), - value: deposit.amount.toNumber(), - }) - - await transaction.fund(inputCoins, { - rate: null, // set null explicitly to always use the default value - changeAddress: depositorAddress, - subtractFee: false, // do not subtract the fee from outputs - }) - - transaction.sign(depositorKeyRing) - - const transactionHash = BitcoinTxHash.from(transaction.txid()) - - return { - transactionHash, - depositUtxo: { - transactionHash, - outputIndex: 0, // The deposit is always the first output. - value: deposit.amount, - }, - rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), - }, - } -} - -/** - * Assembles a Bitcoin locking script for P2(W)SH deposit transaction. - * @param deposit - Details of the deposit. - * @returns Script as an un-prefixed hex string. - */ -export async function assembleDepositScript( - deposit: DepositReceipt -): Promise { - validateDepositReceipt(deposit) - - // All HEXes pushed to the script must be un-prefixed. - const script = new bcoin.Script() - script.clear() - script.pushData(Buffer.from(deposit.depositor.identifierHex, "hex")) - script.pushOp(opcodes.OP_DROP) - script.pushData(Buffer.from(deposit.blindingFactor, "hex")) - script.pushOp(opcodes.OP_DROP) - script.pushOp(opcodes.OP_DUP) - script.pushOp(opcodes.OP_HASH160) - script.pushData(Buffer.from(deposit.walletPublicKeyHash, "hex")) - script.pushOp(opcodes.OP_EQUAL) - script.pushOp(opcodes.OP_IF) - script.pushOp(opcodes.OP_CHECKSIG) - script.pushOp(opcodes.OP_ELSE) - script.pushOp(opcodes.OP_DUP) - script.pushOp(opcodes.OP_HASH160) - script.pushData(Buffer.from(deposit.refundPublicKeyHash, "hex")) - script.pushOp(opcodes.OP_EQUALVERIFY) - script.pushData(Buffer.from(deposit.refundLocktime, "hex")) - script.pushOp(opcodes.OP_CHECKLOCKTIMEVERIFY) - script.pushOp(opcodes.OP_DROP) - script.pushOp(opcodes.OP_CHECKSIG) - script.pushOp(opcodes.OP_ENDIF) - script.compile() - - // Return script as HEX string. - return script.toRaw().toString("hex") -} - -// eslint-disable-next-line valid-jsdoc -/** - * Validates the given deposit receipt. Throws in case of a validation error. - * @param deposit - The validated deposit receipt. - * @dev This function does not validate the depositor's identifier as its - * validity is chain-specific. This parameter must be validated outside. - */ -export function validateDepositReceipt(deposit: DepositReceipt) { - if (deposit.blindingFactor.length != 16) { - throw new Error("Blinding factor must be an 8-byte number") - } - if (deposit.walletPublicKeyHash.length != 40) { - throw new Error("Invalid wallet public key hash") - } - - if (deposit.refundPublicKeyHash.length != 40) { - throw new Error("Invalid refund public key hash") - } - - if (deposit.refundLocktime.length != 8) { - throw new Error("Refund locktime must be a 4-byte number") - } -} - -/** - * Calculates a refund locktime parameter for the given deposit creation timestamp. - * Throws if the resulting locktime is not a 4-byte number. - * @param depositCreatedAt - Unix timestamp in seconds determining the moment - * of deposit creation. - * @param depositRefundLocktimeDuration - Deposit refund locktime duration in seconds. - * @returns A 4-byte little-endian deposit refund locktime as an un-prefixed - * hex string. - */ -export function calculateDepositRefundLocktime( - depositCreatedAt: number, - depositRefundLocktimeDuration: number -): string { - // Locktime is a Unix timestamp in seconds, computed as deposit creation - // timestamp plus locktime duration. - const locktime = BigNumber.from( - depositCreatedAt + depositRefundLocktimeDuration - ) - - const locktimeHex: Hex = Hex.from(locktime.toHexString()) - - if (locktimeHex.toString().length != 8) { - throw new Error("Refund locktime must be a 4 bytes number") - } - - // Bitcoin locktime is interpreted as little-endian integer so we must - // adhere to that convention by converting the locktime accordingly. - return locktimeHex.reverse().toString() -} - -/** - * Calculates a Bitcoin locking script hash for P2(W)SH deposit transaction. - * @param deposit - Details of the deposit. - * @param witness - If true, a witness script hash will be created. - * Otherwise, a legacy script hash will be made. - * @returns Buffer with script hash. - */ -export async function calculateDepositScriptHash( - deposit: DepositReceipt, - witness: boolean -): Promise { - const script = await assembleDepositScript(deposit) - // Parse the script from HEX string. - const parsedScript = bcoin.Script.fromRaw(Buffer.from(script, "hex")) - // If witness script hash should be produced, SHA256 should be used. - // Legacy script hash needs HASH160. - return witness ? parsedScript.sha256() : parsedScript.hash160() -} - -/** - * Calculates a Bitcoin target address for P2(W)SH deposit transaction. - * @param deposit - Details of the deposit. - * @param network - Network that the address should be created for. - * @param witness - If true, a witness address will be created. - * Otherwise, a legacy address will be made. - * @returns Address as string. - */ -export async function calculateDepositAddress( - deposit: DepositReceipt, - network: BitcoinNetwork, - witness: boolean -): Promise { - const scriptHash = await calculateDepositScriptHash(deposit, witness) - const address = witness - ? bcoin.Address.fromWitnessScripthash(scriptHash) - : bcoin.Address.fromScripthash(scriptHash) - return address.toString(toBcoinNetwork(network)) -} - -/** - * Reveals the given deposit to the on-chain Bridge contract. - * @param utxo - Deposit UTXO of the revealed deposit - * @param deposit - Data of the revealed deposit - * @param bitcoinClient - Bitcoin client used to interact with the network - * @param bridge - Handle to the Bridge on-chain contract - * @param vault - vault - * @returns Transaction hash of the reveal deposit transaction as string - * @dev The caller must ensure that the given deposit data are valid and - * the given deposit UTXO actually originates from a deposit transaction - * that matches the given deposit data. - */ -export async function revealDeposit( - utxo: BitcoinUtxo, - deposit: DepositReceipt, - bitcoinClient: BitcoinClient, - bridge: Bridge, - vault?: ChainIdentifier -): Promise { - const depositTx = extractBitcoinRawTxVectors( - await bitcoinClient.getRawTransaction(utxo.transactionHash) - ) - - return await bridge.revealDeposit(depositTx, utxo.outputIndex, deposit, vault) -} - -/** - * Gets a revealed deposit from the bridge. - * @param utxo Deposit UTXO of the revealed deposit - * @param bridge Handle to the Bridge on-chain contract - * @returns Revealed deposit data. - */ -export async function getRevealedDeposit( - utxo: BitcoinUtxo, - bridge: Bridge -): Promise { - return bridge.deposits(utxo.transactionHash, utxo.outputIndex) -} - -/** - * Suggests a wallet that should be used as the deposit target at the given moment. - * @param bridge Handle to the Bridge on-chain contract. - * @returns Compressed (33 bytes long with 02 or 03 prefix) public key of - * the wallet. - */ -export async function suggestDepositWallet( - bridge: Bridge -): Promise { - return bridge.activeWalletPublicKey() -} diff --git a/typescript/src/index.ts b/typescript/src/index.ts index dfc6b3bd1..84130baa0 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -2,13 +2,6 @@ import { validateBitcoinSpvProof } from "./lib/bitcoin" -import { - calculateDepositAddress, - getRevealedDeposit, - revealDeposit, - suggestDepositWallet, -} from "./deposit" - import { submitDepositSweepProof } from "./deposit-sweep" import { @@ -26,10 +19,6 @@ import { } from "./optimistic-minting" export const TBTC = { - calculateDepositAddress, - suggestDepositWallet, - revealDeposit, - getRevealedDeposit, requestRedemption, getRedemptionRequest, findWalletForRedemption, diff --git a/typescript/src/lib/bitcoin/tx.ts b/typescript/src/lib/bitcoin/tx.ts index e51532dc1..6841f4225 100644 --- a/typescript/src/lib/bitcoin/tx.ts +++ b/typescript/src/lib/bitcoin/tx.ts @@ -194,9 +194,37 @@ function locktimeToNumber(locktimeLE: Buffer | string): number { return BigNumber.from(locktimeBE).toNumber() } +/** + * Calculates locktime parameter for the given locktime start timestamp. + * Throws if the resulting locktime is not a 4-byte number. + * @param locktimeStartedAt - Unix timestamp in seconds determining the moment + * of the locktime start. + * @param locktimeDuration Locktime duration in seconds. + * @returns A 4-byte little-endian locktime as an un-prefixed hex string. + */ +function calculateLocktime( + locktimeStartedAt: number, + locktimeDuration: number +): string { + // Locktime is a Unix timestamp in seconds, computed as locktime start + // timestamp plus locktime duration. + const locktime = BigNumber.from(locktimeStartedAt + locktimeDuration) + + const locktimeHex: Hex = Hex.from(locktime.toHexString()) + + if (locktimeHex.toString().length != 8) { + throw new Error("Locktime must be a 4 bytes number") + } + + // Bitcoin locktime is interpreted as little-endian integer, so we must + // adhere to that convention by converting the locktime accordingly. + return locktimeHex.reverse().toString() +} + /** * Utility functions allowing to deal with Bitcoin locktime. */ export const BitcoinLocktimeUtils = { locktimeToNumber, + calculateLocktime, } diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index 503acf77b..52c06f3bd 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -217,6 +217,30 @@ export interface DepositReceipt { refundLocktime: string } +// eslint-disable-next-line valid-jsdoc +/** + * Validates the given deposit receipt. Throws in case of a validation error. + * @param receipt The validated deposit receipt. + * @dev This function does not validate the depositor's identifier as its + * validity is chain-specific. This parameter must be validated outside. + */ +export function validateDepositReceipt(receipt: DepositReceipt) { + if (receipt.blindingFactor.length != 16) { + throw new Error("Blinding factor must be an 8-byte number") + } + if (receipt.walletPublicKeyHash.length != 40) { + throw new Error("Invalid wallet public key hash") + } + + if (receipt.refundPublicKeyHash.length != 40) { + throw new Error("Invalid refund public key hash") + } + + if (receipt.refundLocktime.length != 8) { + throw new Error("Refund locktime must be a 4-byte number") + } +} + /** * Represents a deposit request revealed to the on-chain bridge. */ diff --git a/typescript/src/lib/contracts/index.ts b/typescript/src/lib/contracts/index.ts index 58f2565df..387367de9 100644 --- a/typescript/src/lib/contracts/index.ts +++ b/typescript/src/lib/contracts/index.ts @@ -4,3 +4,18 @@ export * from "./chain-identifier" export * from "./tbtc-token" export * from "./tbtc-vault" export * from "./wallet-registry" + +import { Bridge } from "./bridge" +import { TBTCToken } from "./tbtc-token" +import { TBTCVault } from "./tbtc-vault" +import { WalletRegistry } from "./wallet-registry" + +/** + * Convenience type aggregating all TBTC contracts handles. + */ +export type TBTCContracts = { + bridge: Bridge + tbtcToken: TBTCToken + tbtcVault: TBTCVault + walletRegistry: WalletRegistry +} diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts index 2869fa4a8..277e445ff 100644 --- a/typescript/src/lib/contracts/tbtc-vault.ts +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -8,6 +8,11 @@ import { BigNumber } from "ethers" * Interface for communication with the TBTCVault on-chain contract. */ export interface TBTCVault { + /** + * Gets the chain-specific identifier of this contract. + */ + getChainIdentifier(): ChainIdentifier + /** * Gets optimistic minting delay. * diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index 20a60590b..7a49d3b65 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -6,6 +6,7 @@ import { OptimisticMintingFinalizedEvent, OptimisticMintingRequest, OptimisticMintingRequestedEvent, + ChainIdentifier, } from "../contracts" import { BigNumber, ContractTransaction } from "ethers" import { BitcoinTxHash } from "../bitcoin" @@ -36,6 +37,14 @@ export class EthereumTBTCVault super(config, TBTCVaultDeployment) } + // eslint-disable-next-line valid-jsdoc + /** + * @see {TBTCVault#getChainIdentifier} + */ + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from(this._instance.address) + } + // eslint-disable-next-line valid-jsdoc /** * @see {TBTCVault#optimisticMintingDelay} diff --git a/typescript/src/services/deposits/deposit.ts b/typescript/src/services/deposits/deposit.ts new file mode 100644 index 000000000..4ce3099a3 --- /dev/null +++ b/typescript/src/services/deposits/deposit.ts @@ -0,0 +1,237 @@ +import { + DepositReceipt, + TBTCContracts, + validateDepositReceipt, +} from "../../lib/contracts" +import bcoin from "bcoin" +import { + BitcoinClient, + BitcoinNetwork, + BitcoinTxOutpoint, + BitcoinUtxo, + extractBitcoinRawTxVectors, + toBcoinNetwork, +} from "../../lib/bitcoin" + +const { opcodes } = bcoin.script.common + +/** + * Component representing an instance of the tBTC v2 deposit process. + * Depositing is a complex process spanning both the Bitcoin and the target chain. + * This component tries to abstract away that complexity. + */ +export class Deposit { + /** + * Bitcoin script corresponding to this deposit. + */ + private readonly script: DepositScript + /** + * Handle to tBTC contracts. + */ + private readonly tbtcContracts: TBTCContracts + /** + * Bitcoin client handle. + */ + private readonly bitcoinClient: BitcoinClient + /** + * Bitcoin network the deposit is relevant for. Has an impact on the + * generated deposit address. + */ + public readonly bitcoinNetwork: BitcoinNetwork + + private constructor( + receipt: DepositReceipt, + tbtcContracts: TBTCContracts, + bitcoinClient: BitcoinClient, + bitcoinNetwork: BitcoinNetwork + ) { + this.script = DepositScript.fromReceipt(receipt) + this.tbtcContracts = tbtcContracts + this.bitcoinClient = bitcoinClient + this.bitcoinNetwork = bitcoinNetwork + } + + static async fromReceipt( + receipt: DepositReceipt, + tbtcContracts: TBTCContracts, + bitcoinClient: BitcoinClient + ): Promise { + const bitcoinNetwork = await bitcoinClient.getNetwork() + + return new Deposit(receipt, tbtcContracts, bitcoinClient, bitcoinNetwork) + } + + /** + * @returns Receipt corresponding to this deposit. + */ + getReceipt(): DepositReceipt { + return this.script.receipt + } + + /** + * @returns Bitcoin address corresponding to this deposit. + */ + async getBitcoinAddress(): Promise { + return this.script.deriveAddress(this.bitcoinNetwork) + } + + /** + * Detects Bitcoin funding transactions transferring BTC to this deposit. + * @return Specific UTXOs targeting this deposit. Empty array in case + * there are no UTXOs referring this deposit. + */ + // TODO: Cover with unit tests. + async detectFunding(): Promise { + const utxos = await this.bitcoinClient.findAllUnspentTransactionOutputs( + await this.getBitcoinAddress() + ) + + if (!utxos || utxos.length === 0) { + return [] + } + + return utxos + } + + /** + * Initiates minting of the TBTC token, based on the Bitcoin funding + * transaction outpoint targeting this deposit. By default, it detects and + * uses the outpoint of the recent Bitcoin funding transaction and throws if + * such a transaction does not exist. This behavior can be changed by pointing + * a funding transaction explicitly, using the fundingOutpoint parameter. + * @param fundingOutpoint Optional parameter. Can be used to point + * the funding transaction's outpoint manually. + * @returns Target chain hash of the initiate minting transaction. + * @throws Throws an error if there are no funding transactions while using + * the default funding detection mode. + * @throws Throws an error if the provided funding outpoint does not + * actually refer to this deposit while using the manual funding + * provision mode. + * @throws Throws an error if the funding outpoint was already used to + * initiate minting (both modes). + */ + // TODO: Cover auto funding outpoint detection with unit tests. + async initiateMinting(fundingOutpoint?: BitcoinTxOutpoint): Promise { + let resolvedFundingOutpoint: BitcoinTxOutpoint + + if (typeof fundingOutpoint !== "undefined") { + resolvedFundingOutpoint = fundingOutpoint + } else { + const fundingUtxos = await this.detectFunding() + + if (fundingUtxos.length == 0) { + throw new Error("Deposit not funded yet") + } + + // Take the most recent one. + resolvedFundingOutpoint = fundingUtxos[0] + } + + const { transactionHash, outputIndex } = resolvedFundingOutpoint + + const depositFundingTx = extractBitcoinRawTxVectors( + await this.bitcoinClient.getRawTransaction(transactionHash) + ) + + const { bridge, tbtcVault } = this.tbtcContracts + + return bridge.revealDeposit( + depositFundingTx, + outputIndex, + this.getReceipt(), + tbtcVault.getChainIdentifier() + ) + } +} + +/** + * Represents a Bitcoin script corresponding to a tBTC v2 deposit. + * On a high-level, the script is used to derive the Bitcoin address that is + * used to fund the deposit with BTC. On a low-level, the script is used to + * produce a properly locked funding transaction output that can be unlocked + * by the target wallet during the deposit sweep process. + */ +export class DepositScript { + /** + * Deposit receipt holding the most important information about the deposit + * and allowing to build a unique deposit script (and address) on Bitcoin chain. + */ + public readonly receipt: DepositReceipt + /** + * Flag indicating whether the generated Bitcoin deposit script (and address) + * should be a witness P2WSH one. If false, legacy P2SH will be used instead. + */ + public readonly witness: boolean + + private constructor(receipt: DepositReceipt, witness: boolean) { + validateDepositReceipt(receipt) + + this.receipt = receipt + this.witness = witness + } + + static fromReceipt( + receipt: DepositReceipt, + witness: boolean = true + ): DepositScript { + return new DepositScript(receipt, witness) + } + + /** + * @returns Hashed deposit script as Buffer. + */ + async getHash(): Promise { + const script = await this.getPlainText() + // Parse the script from HEX string. + const parsedScript = bcoin.Script.fromRaw(Buffer.from(script, "hex")) + // If witness script hash should be produced, SHA256 should be used. + // Legacy script hash needs HASH160. + return this.witness ? parsedScript.sha256() : parsedScript.hash160() + } + + /** + * @returns Plain-text deposit script as an un-prefixed hex string. + */ + async getPlainText(): Promise { + // All HEXes pushed to the script must be un-prefixed. + const script = new bcoin.Script() + script.clear() + script.pushData(Buffer.from(this.receipt.depositor.identifierHex, "hex")) + script.pushOp(opcodes.OP_DROP) + script.pushData(Buffer.from(this.receipt.blindingFactor, "hex")) + script.pushOp(opcodes.OP_DROP) + script.pushOp(opcodes.OP_DUP) + script.pushOp(opcodes.OP_HASH160) + script.pushData(Buffer.from(this.receipt.walletPublicKeyHash, "hex")) + script.pushOp(opcodes.OP_EQUAL) + script.pushOp(opcodes.OP_IF) + script.pushOp(opcodes.OP_CHECKSIG) + script.pushOp(opcodes.OP_ELSE) + script.pushOp(opcodes.OP_DUP) + script.pushOp(opcodes.OP_HASH160) + script.pushData(Buffer.from(this.receipt.refundPublicKeyHash, "hex")) + script.pushOp(opcodes.OP_EQUALVERIFY) + script.pushData(Buffer.from(this.receipt.refundLocktime, "hex")) + script.pushOp(opcodes.OP_CHECKLOCKTIMEVERIFY) + script.pushOp(opcodes.OP_DROP) + script.pushOp(opcodes.OP_CHECKSIG) + script.pushOp(opcodes.OP_ENDIF) + script.compile() + + // Return script as HEX string. + return script.toRaw().toString("hex") + } + + /** + * Derives a Bitcoin address for the given network for this deposit script. + * @param bitcoinNetwork Bitcoin network the address should be derived for. + * @returns Bitcoin address corresponding to this deposit script. + */ + async deriveAddress(bitcoinNetwork: BitcoinNetwork): Promise { + const scriptHash = await this.getHash() + const address = this.witness + ? bcoin.Address.fromWitnessScripthash(scriptHash) + : bcoin.Address.fromScripthash(scriptHash) + return address.toString(toBcoinNetwork(bitcoinNetwork)) + } +} diff --git a/typescript/src/services/deposits/deposits-service.ts b/typescript/src/services/deposits/deposits-service.ts new file mode 100644 index 000000000..8b6a2e3be --- /dev/null +++ b/typescript/src/services/deposits/deposits-service.ts @@ -0,0 +1,97 @@ +import { + ChainIdentifier, + DepositReceipt, + TBTCContracts, +} from "../../lib/contracts" +import { + BitcoinAddressConverter, + BitcoinClient, + BitcoinHashUtils, + BitcoinLocktimeUtils, +} from "../../lib/bitcoin" +import { Deposit } from "./deposit" +import * as crypto from "crypto" + +/** + * Service exposing features related to tBTC v2 deposits. + */ +export class DepositsService { + /** + * Deposit refund locktime duration in seconds. + * This is 9 month in seconds assuming 1 month = 30 days + */ + private readonly depositRefundLocktimeDuration = 23328000 + /** + * Handle to tBTC contracts. + */ + private readonly tbtcContracts: TBTCContracts + /** + * Bitcoin client handle. + */ + private readonly bitcoinClient: BitcoinClient + + constructor(tbtcContracts: TBTCContracts, bitcoinClient: BitcoinClient) { + this.tbtcContracts = tbtcContracts + this.bitcoinClient = bitcoinClient + } + + /** + * Initiates a new deposit for the given depositor and Bitcoin recovery address. + * @param depositor Identifier of the depositor specific for the target chain + * deposited BTC are bridged to. For example, this is a + * 20-byte address on Ethereum. + * @param bitcoinRecoveryAddress P2PKH or P2WPKH Bitcoin address that can + * be used for emergency recovery of the + * deposited funds. + * @returns Handle to the initiated deposit. + */ + // TODO: Accept depositor as string and automatically validate & convert OR + // explore the possibility of fetching this from the account instance. + // TODO: Cover with unit tests. + async initiateDeposit( + depositor: ChainIdentifier, + bitcoinRecoveryAddress: string + ): Promise { + const receipt = await this.generateDepositReceipt( + depositor, + bitcoinRecoveryAddress + ) + + return Deposit.fromReceipt(receipt, this.tbtcContracts, this.bitcoinClient) + } + + private async generateDepositReceipt( + depositor: ChainIdentifier, + bitcoinRecoveryAddress: string + ): Promise { + const blindingFactor = crypto.randomBytes(8).toString("hex") + + const walletPublicKey = + await this.tbtcContracts.bridge.activeWalletPublicKey() + + if (!walletPublicKey) { + throw new Error("Could not get active wallet public key") + } + + const walletPublicKeyHash = BitcoinHashUtils.computeHash160(walletPublicKey) + + const refundPublicKeyHash = BitcoinAddressConverter.addressToPublicKeyHash( + bitcoinRecoveryAddress + ) + + const currentTimestamp = Math.floor(new Date().getTime() / 1000) + + const refundLocktime = BitcoinLocktimeUtils.calculateLocktime( + currentTimestamp, + this.depositRefundLocktimeDuration + ) + + return { + depositor, + blindingFactor, + walletPublicKeyHash, + refundPublicKeyHash, + refundLocktime, + } + } +} diff --git a/typescript/src/services/deposits/funding.ts b/typescript/src/services/deposits/funding.ts new file mode 100644 index 000000000..a13cc86f5 --- /dev/null +++ b/typescript/src/services/deposits/funding.ts @@ -0,0 +1,155 @@ +import { DepositScript } from "./deposit" +import { + BitcoinClient, + BitcoinPrivateKeyUtils, + BitcoinRawTx, + BitcoinTxHash, + BitcoinUtxo, +} from "../../lib/bitcoin" +import { BigNumber } from "ethers" +import bcoin from "bcoin" + +/** + * Component allowing to craft and submit the Bitcoin funding transaction using + * the given tBTC v2 deposit script. + * + * @experimental THIS IS EXPERIMENTAL CODE THAT CAN BE CHANGED OR REMOVED + * IN FUTURE RELEASES. IT SHOULD BE USED ONLY FOR INTERNAL + * PURPOSES AND EXTERNAL APPLICATIONS SHOULD NOT DEPEND ON IT. + */ +// TODO: Abstract away transaction signing so there is no need to deal with +// private key directly. +export class DepositFunding { + public readonly script: DepositScript + + private constructor(script: DepositScript) { + this.script = script + } + + static fromScript(script: DepositScript): DepositFunding { + return new DepositFunding(script) + } + + /** + * Assembles and signs the Bitcoin P2(W)SH funding transaction using + * the underlying deposit script. + * @dev It is up to the caller to ensure that input UTXOs are valid and + * can be unlocked using the depositor's private key. It is also + * caller's responsibility to ensure the given deposit is funded exactly + * once. + * @param amount Deposit amount in satoshis. + * @param inputUtxos UTXOs that should be used as transaction inputs. + * @param depositorPrivateKey Bitcoin private key of the depositor. Must + * be able to unlock input UTXOs. + * @returns The outcome consisting of: + * - the deposit transaction hash, + * - the deposit UTXO produced by this transaction. + * - the deposit transaction in the raw format + */ + async assembleTransaction( + amount: BigNumber, + inputUtxos: (BitcoinUtxo & BitcoinRawTx)[], + depositorPrivateKey: string + ): Promise<{ + transactionHash: BitcoinTxHash + depositUtxo: BitcoinUtxo + rawTransaction: BitcoinRawTx + }> { + const depositorKeyRing = + BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) + const depositorAddress = depositorKeyRing.getAddress("string") + + const inputCoins = inputUtxos.map((utxo) => + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), + utxo.outputIndex, + -1 + ) + ) + + const transaction = new bcoin.MTX() + + const scriptHash = await this.script.getHash() + + transaction.addOutput({ + script: this.script.witness + ? bcoin.Script.fromProgram(0, scriptHash) + : bcoin.Script.fromScripthash(scriptHash), + value: amount.toNumber(), + }) + + await transaction.fund(inputCoins, { + rate: null, // set null explicitly to always use the default value + changeAddress: depositorAddress, + subtractFee: false, // do not subtract the fee from outputs + }) + + transaction.sign(depositorKeyRing) + + const transactionHash = BitcoinTxHash.from(transaction.txid()) + + return { + transactionHash, + depositUtxo: { + transactionHash, + outputIndex: 0, // The deposit is always the first output. + value: amount, + }, + rawTransaction: { + transactionHex: transaction.toRaw().toString("hex"), + }, + } + } + + /** + * Assembles, signs and submits the Bitcoin P2(W)SH funding transaction + * using the underlying deposit script. + * @dev It is up to the caller to ensure that depositor's private key controls + * some UTXOs that can be used as input. It is also caller's responsibility + * to ensure the given deposit is funded exactly once. + * @param amount Deposit amount in satoshis. + * @param depositorPrivateKey Bitcoin private key of the depositor. + * @param bitcoinClient Bitcoin client used to interact with the network. + * @returns The outcome consisting of: + * - the deposit transaction hash, + * - the deposit UTXO produced by this transaction. + */ + async submitTransaction( + amount: BigNumber, + depositorPrivateKey: string, + bitcoinClient: BitcoinClient + ): Promise<{ + transactionHash: BitcoinTxHash + depositUtxo: BitcoinUtxo + }> { + const depositorKeyRing = + BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) + const depositorAddress = depositorKeyRing.getAddress("string") + + const utxos = await bitcoinClient.findAllUnspentTransactionOutputs( + depositorAddress + ) + + const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] + for (const utxo of utxos) { + const utxoRawTransaction = await bitcoinClient.getRawTransaction( + utxo.transactionHash + ) + + utxosWithRaw.push({ + ...utxo, + transactionHex: utxoRawTransaction.transactionHex, + }) + } + + const { transactionHash, depositUtxo, rawTransaction } = + await this.assembleTransaction(amount, utxosWithRaw, depositorPrivateKey) + + await bitcoinClient.broadcast(rawTransaction) + + return { + transactionHash, + depositUtxo, + } + } +} diff --git a/typescript/src/services/deposits/index.ts b/typescript/src/services/deposits/index.ts new file mode 100644 index 000000000..f75596a11 --- /dev/null +++ b/typescript/src/services/deposits/index.ts @@ -0,0 +1,3 @@ +export * from "./deposit" +export * from "./deposits-service" +export * from "./funding" diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index 7310d28b6..6b03fff4b 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -10,7 +10,6 @@ import { BitcoinCompactSizeUint, BitcoinAddressConverter, } from "../src/lib/bitcoin" -import { calculateDepositRefundLocktime } from "../src/deposit" import { Hex } from "../src/lib/utils" import { BigNumber } from "ethers" import { btcAddresses } from "./data/bitcoin" @@ -384,32 +383,31 @@ describe("Bitcoin", () => { }) describe("BitcoinLocktimeUtils", () => { - const { locktimeToNumber } = BitcoinLocktimeUtils + const { locktimeToNumber, calculateLocktime } = BitcoinLocktimeUtils describe("locktimeToNumber", () => { - const depositCreatedAt: number = 1640181600 - const depositRefundLocktimeDuration: number = 2592000 - const depositRefundLocktime = calculateDepositRefundLocktime( - depositCreatedAt, - depositRefundLocktimeDuration + const locktimeStartedAt: number = 1640181600 + const locktimeDuration: number = 2592000 + const locktime = BitcoinLocktimeUtils.calculateLocktime( + locktimeStartedAt, + locktimeDuration ) const testData = [ { contextName: "when locktime is a block height", unprefixedHex: "ede80600", - expectedDepositLocktime: 452845, + expectedLocktime: 452845, }, { contextName: "when locktime is a timestamp", unprefixedHex: "06241559", - expectedDepositLocktime: 1494557702, + expectedLocktime: 1494557702, }, { - contextName: "for deposit refund locktime", - unprefixedHex: depositRefundLocktime, - expectedDepositLocktime: - depositCreatedAt + depositRefundLocktimeDuration, + contextName: "for locktime", + unprefixedHex: locktime, + expectedLocktime: locktimeStartedAt + locktimeDuration, }, ] @@ -418,7 +416,7 @@ describe("Bitcoin", () => { context("when input is non-prefixed hex string", () => { it("should return the locktime in seconds", async () => { expect(locktimeToNumber(test.unprefixedHex)).to.be.equal( - test.expectedDepositLocktime + test.expectedLocktime ) }) }) @@ -426,7 +424,7 @@ describe("Bitcoin", () => { context("when input is 0x prefixed hex string", () => { it("should return the locktime in seconds", async () => { expect(locktimeToNumber("0x" + test.unprefixedHex)).to.be.equal( - test.expectedDepositLocktime + test.expectedLocktime ) }) }) @@ -435,12 +433,45 @@ describe("Bitcoin", () => { it("should return the locktime in seconds", async () => { expect( locktimeToNumber(Buffer.from(test.unprefixedHex, "hex")) - ).to.be.equal(test.expectedDepositLocktime) + ).to.be.equal(test.expectedLocktime) }) }) }) }) }) + + describe("calculateLocktime", () => { + context("when the resulting locktime is lesser than 4 bytes", () => { + it("should throw", () => { + // This will result with 2592001 as the locktime which is a 3-byte number. + expect(() => calculateLocktime(1, 2592000)).to.throw( + "Locktime must be a 4 bytes number" + ) + }) + }) + + context("when the resulting locktime is greater than 4 bytes", () => { + it("should throw", () => { + // This will result with 259200144444 as the locktime which is a 5-byte number. + expect(() => calculateLocktime(259197552444, 2592000)).to.throw( + "Locktime must be a 4 bytes number" + ) + }) + }) + + context("when the resulting locktime is a 4-byte number", () => { + it("should compute a proper 4-byte little-endian locktime as un-prefixed hex string", () => { + const locktimeStartedAt = 1652776752 + + const locktime = calculateLocktime(locktimeStartedAt, 2592000) + + // The start timestamp is 1652776752 and locktime duration 2592000 (30 days). + // So, the locktime timestamp is 1652776752 + 2592000 = 1655368752 which + // is represented as 30ecaa62 hex in the little-endian format. + expect(locktime).to.be.equal("30ecaa62") + }) + }) + }) }) describe("BitcoinHeaderSerializer", () => { diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index 4edde113c..57a41762d 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -1,7 +1,11 @@ import { BigNumber } from "ethers" -import { BitcoinRawTx, BitcoinUtxo, BitcoinTxHash } from "../../src/lib/bitcoin" +import { + BitcoinRawTx, + BitcoinUtxo, + BitcoinTxHash, + BitcoinLocktimeUtils, +} from "../../src/lib/bitcoin" import { DepositReceipt } from "../../src/lib/contracts" -import { calculateDepositRefundLocktime } from "../../src/deposit" import { EthereumAddress } from "../../src/lib/ethereum" /** @@ -58,7 +62,10 @@ export const depositRefundOfWitnessDepositAndWitnessRefunderAddress: DepositRefu walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1674820800, 3600), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1674820800, + 3600 + ), }, }, // witness address associated with the refunder's private key @@ -112,7 +119,10 @@ export const depositRefundOfNonWitnessDepositAndWitnessRefunderAddress: DepositR walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1674820800, 3600), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1674820800, + 3600 + ), }, }, // witness address associated with the refunder's private key @@ -166,7 +176,10 @@ export const depositRefundOfWitnessDepositAndNonWitnessRefunderAddress: DepositR walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", refundPublicKeyHash: "1b67f27537c7b30a23d8ccefb96a4cacfc72d9a1", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1674820800, 3600), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1674820800, + 3600 + ), }, }, // non-witness address associated with the refunder's private key diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index fd85dcd64..14251228b 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -6,9 +6,9 @@ import { BitcoinUtxo, BitcoinTxMerkleBranch, BitcoinTxHash, + BitcoinLocktimeUtils, } from "../../src/lib/bitcoin" import { DepositReceipt } from "../../src/lib/contracts" -import { calculateDepositRefundLocktime } from "../../src/deposit" import { BigNumber } from "ethers" import { EthereumAddress } from "../../src/lib/ethereum" import { Hex } from "../../src" @@ -69,7 +69,10 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. refundPublicKeyHash: "e257eccafbc07c381642ce6e7e55120fb077fbed", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1641650400, 2592000), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1641650400, + 2592000 + ), }, }, { @@ -97,7 +100,10 @@ export const depositSweepWithNoMainUtxoAndWitnessOutput: DepositSweepTestData = // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. refundPublicKeyHash: "e257eccafbc07c381642ce6e7e55120fb077fbed", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1641650400, 2592000), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1641650400, + 2592000 + ), }, }, ], @@ -156,7 +162,10 @@ export const depositSweepWithNoMainUtxoAndNonWitnessOutput: DepositSweepTestData // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. refundPublicKeyHash: "e257eccafbc07c381642ce6e7e55120fb077fbed", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1653302600, 2592000), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1653302600, + 2592000 + ), }, }, ], @@ -208,7 +217,10 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. refundPublicKeyHash: "e257eccafbc07c381642ce6e7e55120fb077fbed", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1641650400, 2592000), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1641650400, + 2592000 + ), }, }, { @@ -237,7 +249,10 @@ export const depositSweepWithWitnessMainUtxoAndWitnessOutput: DepositSweepTestDa // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. refundPublicKeyHash: "e257eccafbc07c381642ce6e7e55120fb077fbed", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1641650400, 2592000), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1641650400, + 2592000 + ), }, }, ], @@ -332,7 +347,10 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes // HASH160 of 039d61d62dcd048d3f8550d22eb90b4af908db60231d117aeede04e7bc11907bfa. refundPublicKeyHash: "e257eccafbc07c381642ce6e7e55120fb077fbed", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime(1653302600, 2592000), + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( + 1653302600, + 2592000 + ), }, }, ], diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index 5d327fe58..3b179c4c5 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -1,5 +1,5 @@ import { expect } from "chai" -import { BigNumber, BigNumberish } from "ethers" +import { BigNumber } from "ethers" import { testnetAddress, testnetPrivateKey, @@ -8,43 +8,38 @@ import { testnetUTXO, } from "./data/deposit" import { - extractBitcoinRawTxVectors, + BitcoinLocktimeUtils, BitcoinRawTx, BitcoinTxHash, BitcoinUtxo, + extractBitcoinRawTxVectors, } from "../src/lib/bitcoin" -import { DepositRequest } from "../src/lib/contracts" +import { DepositReceipt } from "../src/lib/contracts" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import bcoin from "bcoin" -import { - assembleDepositScript, - assembleDepositTransaction, - calculateDepositAddress, - calculateDepositRefundLocktime, - calculateDepositScriptHash, - DepositReceiptWithAmount, - getRevealedDeposit, - revealDeposit, - submitDepositTransaction, - suggestDepositWallet, -} from "../src/deposit" -import { MockBridge } from "./utils/mock-bridge" import { EthereumAddress } from "../src/lib/ethereum" import { BitcoinNetwork } from "../src" +import { + DepositFunding, + DepositScript, + Deposit, +} from "../src/services/deposits" +import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" describe("Deposit", () => { const depositCreatedAt: number = 1640181600 const depositRefundLocktimeDuration: number = 2592000 - const deposit: DepositReceiptWithAmount = { + const depositAmount = BigNumber.from(10000) // 0.0001 BTC + + const deposit: DepositReceipt = { depositor: EthereumAddress.from("934b98637ca318a4d6e7ca6ffd1690b8e77df637"), - amount: BigNumber.from(10000), // 0.0001 BTC // HASH160 of 03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9. walletPublicKeyHash: "8db50eb52063ea9d98b3eac91489a90f738986f6", // HASH160 of 0300d6f28a2f6bf9836f57fcda5d284c9a8f849316119779f0d6090830d97763a9. refundPublicKeyHash: "28e081f285138ccbe389c1eb8985716230129f89", blindingFactor: "f9f0c90d00039523", - refundLocktime: calculateDepositRefundLocktime( + refundLocktime: BitcoinLocktimeUtils.calculateLocktime( depositCreatedAt, depositRefundLocktimeDuration ), @@ -211,572 +206,511 @@ describe("Deposit", () => { expect(script.substring(182, 184)).to.be.equal("68") } - describe("submitDepositTransaction", () => { - let bitcoinClient: MockBitcoinClient - - beforeEach(async () => { - bcoin.set("testnet") - - bitcoinClient = new MockBitcoinClient() - - // Tie used testnetAddress with testnetUTXO to use it during deposit - // creation. - const utxos = new Map() - utxos.set(testnetAddress, [testnetUTXO]) - bitcoinClient.unspentTransactionOutputs = utxos - - // Tie testnetTransaction to testnetUTXO. This is needed since - // submitDepositTransaction attach transaction data to each UTXO. - const rawTransactions = new Map() - rawTransactions.set(testnetTransactionHash.toString(), testnetTransaction) - bitcoinClient.rawTransactions = rawTransactions - }) - - context("when witness option is true", () => { - let transactionHash: BitcoinTxHash - let depositUtxo: BitcoinUtxo + describe("DepositFunding", () => { + describe("submitTransaction", () => { + let bitcoinClient: MockBitcoinClient beforeEach(async () => { - ;({ transactionHash, depositUtxo } = await submitDepositTransaction( - deposit, - testnetPrivateKey, - bitcoinClient, - true - )) - }) - - it("should broadcast P2WSH transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - expectedP2WSHDeposit.transaction + bcoin.set("testnet") + + bitcoinClient = new MockBitcoinClient() + + // Tie used testnetAddress with testnetUTXO to use it during deposit + // creation. + const utxos = new Map() + utxos.set(testnetAddress, [testnetUTXO]) + bitcoinClient.unspentTransactionOutputs = utxos + + // Tie testnetTransaction to testnetUTXO. This is needed since + // submitDepositTransaction attach transaction data to each UTXO. + const rawTransactions = new Map() + rawTransactions.set( + testnetTransactionHash.toString(), + testnetTransaction ) + bitcoinClient.rawTransactions = rawTransactions }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - expectedP2WSHDeposit.transactionHash - ) - }) - - it("should return the proper deposit UTXO", () => { - const expectedDepositUtxo = { - transactionHash: expectedP2WSHDeposit.transactionHash, - outputIndex: 0, - value: deposit.amount, - } - - expect(depositUtxo).to.be.eql(expectedDepositUtxo) - }) - }) - - context("when witness option is false", () => { - let transactionHash: BitcoinTxHash - let depositUtxo: BitcoinUtxo - - beforeEach(async () => { - ;({ transactionHash, depositUtxo } = await submitDepositTransaction( - deposit, - testnetPrivateKey, - bitcoinClient, - false - )) - }) + context("when witness option is true", () => { + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo - it("should broadcast P2SH transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - expectedP2SHDeposit.transaction - ) - }) + beforeEach(async () => { + const depositFunding = DepositFunding.fromScript( + DepositScript.fromReceipt(deposit, true) + ) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - expectedP2SHDeposit.transactionHash - ) - }) + ;({ transactionHash, depositUtxo } = + await depositFunding.submitTransaction( + depositAmount, + testnetPrivateKey, + bitcoinClient + )) + }) - it("should return the proper deposit UTXO", () => { - const expectedDepositUtxo = { - transactionHash: expectedP2SHDeposit.transactionHash, - outputIndex: 0, - value: deposit.amount, - } + it("should broadcast P2WSH transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + expectedP2WSHDeposit.transaction + ) + }) - expect(depositUtxo).to.be.eql(expectedDepositUtxo) - }) - }) - }) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + expectedP2WSHDeposit.transactionHash + ) + }) - describe("assembleDepositTransaction", () => { - context("when witness option is true", () => { - let transactionHash: BitcoinTxHash - let depositUtxo: BitcoinUtxo - let transaction: BitcoinRawTx + it("should return the proper deposit UTXO", () => { + const expectedDepositUtxo = { + transactionHash: expectedP2WSHDeposit.transactionHash, + outputIndex: 0, + value: depositAmount, + } - beforeEach(async () => { - ;({ - transactionHash, - depositUtxo, - rawTransaction: transaction, - } = await assembleDepositTransaction( - deposit, - [testnetUTXO], - testnetPrivateKey, - true - )) + expect(depositUtxo).to.be.eql(expectedDepositUtxo) + }) }) - it("should return P2WSH transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql(expectedP2WSHDeposit.transaction) + context("when witness option is false", () => { + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + beforeEach(async () => { + const depositFunding = DepositFunding.fromScript( + DepositScript.fromReceipt(deposit, false) + ) - expect(txJSON.hash).to.be.equal( - expectedP2WSHDeposit.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) + ;({ transactionHash, depositUtxo } = + await depositFunding.submitTransaction( + depositAmount, + testnetPrivateKey, + bitcoinClient + )) + }) - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) + it("should broadcast P2SH transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + expectedP2SHDeposit.transaction + ) + }) - const input = txJSON.inputs[0] + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + expectedP2SHDeposit.transactionHash + ) + }) - expect(input.prevout.hash).to.be.equal( - testnetUTXO.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(testnetAddress) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) - - const depositOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] - - // Value should correspond to the deposit amount. - expect(depositOutput.value).to.be.equal(deposit.amount.toNumber()) - // Should be OP_0 . The script hash is the same as in - // expectedP2WSHDeposit.scriptHash (see calculateDepositScriptHash - // witness scenario) and it should be prefixed with its byte length: - // 0x20. The OP_0 opcode is 0x00. - expect(depositOutput.script).to.be.equal( - `0020${expectedP2WSHDeposit.scriptHash}` - ) - // The address should correspond to the script hash - // expectedP2WSHDeposit.scriptHash on testnet so it should be: - // expectedP2WSHDeposit.testnetAddress (see calculateDepositAddress - // witness scenario). - expect(depositOutput.address).to.be.equal( - expectedP2WSHDeposit.testnetAddress - ) + it("should return the proper deposit UTXO", () => { + const expectedDepositUtxo = { + transactionHash: expectedP2SHDeposit.transactionHash, + outputIndex: 0, + value: depositAmount, + } - // Change value should be equal to: inputValue - depositAmount - fee. - expect(changeOutput.value).to.be.equal(3921680) - // Should be OP_0 . Public key corresponds to - // depositor BTC address. - expect(changeOutput.script).to.be.equal( - "00147ac2d9378a1c47e589dfb8095ca95ed2140d2726" - ) - // Should return the change to depositor BTC address. - expect(changeOutput.address).to.be.equal(testnetAddress) + expect(depositUtxo).to.be.eql(expectedDepositUtxo) + }) }) + }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - expectedP2WSHDeposit.transactionHash - ) - }) + describe("assembleTransaction", () => { + context("when witness option is true", () => { + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo + let transaction: BitcoinRawTx - it("should return the proper deposit UTXO", () => { - const expectedDepositUtxo = { - transactionHash: expectedP2WSHDeposit.transactionHash, - outputIndex: 0, - value: deposit.amount, - } + beforeEach(async () => { + const depositFunding = DepositFunding.fromScript( + DepositScript.fromReceipt(deposit, true) + ) - expect(depositUtxo).to.be.eql(expectedDepositUtxo) - }) - }) + ;({ + transactionHash, + depositUtxo, + rawTransaction: transaction, + } = await depositFunding.assembleTransaction( + depositAmount, + [testnetUTXO], + testnetPrivateKey + )) + }) - context("when witness option is false", () => { - let transactionHash: BitcoinTxHash - let depositUtxo: BitcoinUtxo - let transaction: BitcoinRawTx + it("should return P2WSH transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql(expectedP2WSHDeposit.transaction) - beforeEach(async () => { - ;({ - transactionHash, - depositUtxo, - rawTransaction: transaction, - } = await assembleDepositTransaction( - deposit, - [testnetUTXO], - testnetPrivateKey, - false - )) - }) + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - it("should return P2SH transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql(expectedP2SHDeposit.transaction) + expect(txJSON.hash).to.be.equal( + expectedP2WSHDeposit.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) - expect(txJSON.hash).to.be.equal( - expectedP2SHDeposit.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) + const input = txJSON.inputs[0] - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) + expect(input.prevout.hash).to.be.equal( + testnetUTXO.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(testnetAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const depositOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // Value should correspond to the deposit amount. + expect(depositOutput.value).to.be.equal(depositAmount.toNumber()) + // Should be OP_0 . The script hash is the same as in + // expectedP2WSHDeposit.scriptHash (see DepositScript.getHash + // witness scenario) and it should be prefixed with its byte length: + // 0x20. The OP_0 opcode is 0x00. + expect(depositOutput.script).to.be.equal( + `0020${expectedP2WSHDeposit.scriptHash}` + ) + // The address should correspond to the script hash + // expectedP2WSHDeposit.scriptHash on testnet so it should be: + // expectedP2WSHDeposit.testnetAddress (see DepositScript.deriveAddress + // witness scenario). + expect(depositOutput.address).to.be.equal( + expectedP2WSHDeposit.testnetAddress + ) - const input = txJSON.inputs[0] + // Change value should be equal to: inputValue - depositAmount - fee. + expect(changeOutput.value).to.be.equal(3921680) + // Should be OP_0 . Public key corresponds to + // depositor BTC address. + expect(changeOutput.script).to.be.equal( + "00147ac2d9378a1c47e589dfb8095ca95ed2140d2726" + ) + // Should return the change to depositor BTC address. + expect(changeOutput.address).to.be.equal(testnetAddress) + }) - expect(input.prevout.hash).to.be.equal( - testnetUTXO.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(testnetAddress) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) - - const depositOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] - - // Value should correspond to the deposit amount. - expect(depositOutput.value).to.be.equal(deposit.amount.toNumber()) - // Should be OP_HASH160 OP_EQUAL. The script hash is - // expectedP2SHDeposit.scriptHash (see calculateDepositScriptHash - // non-witness scenario) and it should be prefixed with its byte - // length: 0x14. The OP_HASH160 opcode is 0xa9 and OP_EQUAL is 0x87. - expect(depositOutput.script).to.be.equal( - `a914${expectedP2SHDeposit.scriptHash}87` - ) - // The address should correspond to the script hash - // expectedP2SHDeposit.scriptHash on testnet so it should be - // expectedP2SHDeposit.testnetAddress (see calculateDepositAddress - // non-witness scenario). - expect(depositOutput.address).to.be.equal( - expectedP2SHDeposit.testnetAddress - ) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + expectedP2WSHDeposit.transactionHash + ) + }) - // Change value should be equal to: inputValue - depositAmount - fee. - expect(changeOutput.value).to.be.equal(3921790) - // Should be OP_0 . Public key corresponds to - // depositor BTC address. - expect(changeOutput.script).to.be.equal( - "00147ac2d9378a1c47e589dfb8095ca95ed2140d2726" - ) - // Should return the change to depositor BTC address. - expect(changeOutput.address).to.be.equal(testnetAddress) - }) + it("should return the proper deposit UTXO", () => { + const expectedDepositUtxo = { + transactionHash: expectedP2WSHDeposit.transactionHash, + outputIndex: 0, + value: depositAmount, + } - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - expectedP2SHDeposit.transactionHash - ) + expect(depositUtxo).to.be.eql(expectedDepositUtxo) + }) }) - it("should return the proper deposit UTXO", () => { - const expectedDepositUtxo = { - transactionHash: expectedP2SHDeposit.transactionHash, - outputIndex: 0, - value: deposit.amount, - } + context("when witness option is false", () => { + let transactionHash: BitcoinTxHash + let depositUtxo: BitcoinUtxo + let transaction: BitcoinRawTx - expect(depositUtxo).to.be.deep.equal(expectedDepositUtxo) - }) - }) - }) + beforeEach(async () => { + const depositFunding = DepositFunding.fromScript( + DepositScript.fromReceipt(deposit, false) + ) - describe("assembleDepositScript", () => { - let script: string + ;({ + transactionHash, + depositUtxo, + rawTransaction: transaction, + } = await depositFunding.assembleTransaction( + depositAmount, + [testnetUTXO], + testnetPrivateKey + )) + }) - beforeEach(async () => { - script = await assembleDepositScript(deposit) - }) + it("should return P2SH transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql(expectedP2SHDeposit.transaction) - it("should return script with proper structure", async () => { - assertValidDepositScript(script) - }) - }) + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - describe("calculateDepositRefundLocktime", () => { - context("when the resulting locktime is lesser than 4 bytes", () => { - it("should throw", () => { - // This will result with 2592001 as the locktime which is a 3-byte number. - expect(() => calculateDepositRefundLocktime(1, 2592000)).to.throw( - "Refund locktime must be a 4 bytes number" - ) - }) - }) + expect(txJSON.hash).to.be.equal( + expectedP2SHDeposit.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) - context("when the resulting locktime is greater than 4 bytes", () => { - it("should throw", () => { - // This will result with 259200144444 as the locktime which is a 5-byte number. - expect(() => - calculateDepositRefundLocktime(259197552444, 2592000) - ).to.throw("Refund locktime must be a 4 bytes number") - }) - }) + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) - context("when the resulting locktime is a 4-byte number", () => { - it("should compute a proper 4-byte little-endian locktime as un-prefixed hex string", () => { - const depositCreatedAt = 1652776752 + const input = txJSON.inputs[0] - const refundLocktime = calculateDepositRefundLocktime( - depositCreatedAt, - 2592000 - ) + expect(input.prevout.hash).to.be.equal( + testnetUTXO.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(testnetAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const depositOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // Value should correspond to the deposit amount. + expect(depositOutput.value).to.be.equal(depositAmount.toNumber()) + // Should be OP_HASH160 OP_EQUAL. The script hash is + // expectedP2SHDeposit.scriptHash (see DepositScript.getHash + // non-witness scenario) and it should be prefixed with its byte + // length: 0x14. The OP_HASH160 opcode is 0xa9 and OP_EQUAL is 0x87. + expect(depositOutput.script).to.be.equal( + `a914${expectedP2SHDeposit.scriptHash}87` + ) + // The address should correspond to the script hash + // expectedP2SHDeposit.scriptHash on testnet so it should be + // expectedP2SHDeposit.testnetAddress (see DepositScript.deriveAddress + // non-witness scenario). + expect(depositOutput.address).to.be.equal( + expectedP2SHDeposit.testnetAddress + ) - // The creation timestamp is 1652776752 and locktime duration 2592000 (30 days). - // So, the locktime timestamp is 1652776752 + 2592000 = 1655368752 which - // is represented as 30ecaa62 hex in the little-endian format. - expect(refundLocktime).to.be.equal("30ecaa62") - }) - }) - }) + // Change value should be equal to: inputValue - depositAmount - fee. + expect(changeOutput.value).to.be.equal(3921790) + // Should be OP_0 . Public key corresponds to + // depositor BTC address. + expect(changeOutput.script).to.be.equal( + "00147ac2d9378a1c47e589dfb8095ca95ed2140d2726" + ) + // Should return the change to depositor BTC address. + expect(changeOutput.address).to.be.equal(testnetAddress) + }) - describe("calculateDepositScriptHash", () => { - context("when witness option is true", () => { - let scriptHash: Buffer + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + expectedP2SHDeposit.transactionHash + ) + }) - beforeEach(async () => { - scriptHash = await calculateDepositScriptHash(deposit, true) - }) + it("should return the proper deposit UTXO", () => { + const expectedDepositUtxo = { + transactionHash: expectedP2SHDeposit.transactionHash, + outputIndex: 0, + value: depositAmount, + } - it("should return proper witness script hash", async () => { - // The script for given deposit should be the same as in - // assembleDepositScript test scenario i.e. expectedDepositScript. - // The hash of this script should correspond to the OP_SHA256 opcode - // which applies SHA-256 on the input. In this case the hash is - // expectedP2WSHDeposit.scriptHash and it can be verified with - // the following command: - // echo -n $SCRIPT | xxd -r -p | openssl dgst -sha256 - expect(scriptHash.toString("hex")).to.be.equal( - expectedP2WSHDeposit.scriptHash - ) + expect(depositUtxo).to.be.deep.equal(expectedDepositUtxo) + }) }) }) + }) - context("when witness option is false", () => { - let scriptHash: Buffer + describe("DepositScript", () => { + describe("getPlainText", () => { + let script: string beforeEach(async () => { - scriptHash = await calculateDepositScriptHash(deposit, false) + script = await DepositScript.fromReceipt(deposit).getPlainText() }) - it("should return proper non-witness script hash", async () => { - // The script for given deposit should be the same as in - // assembleDepositScript test scenario i.e. expectedDepositScript. - // The hash of this script should correspond to the OP_HASH160 opcode - // which applies SHA-256 and then RIPEMD-160 on the input. In this case - // the hash is expectedP2SHDeposit.scriptHash and it can be verified - // with the following command: - // echo -n $SCRIPT | xxd -r -p | openssl dgst -sha256 -binary | openssl dgst -rmd160 - expect(scriptHash.toString("hex")).to.be.equal( - expectedP2SHDeposit.scriptHash - ) + it("should return script with proper structure", async () => { + assertValidDepositScript(script) }) }) - }) - - describe("calculateDepositAddress", () => { - let address: string - context("when network is main", () => { + describe("getHash", () => { context("when witness option is true", () => { + let scriptHash: Buffer + beforeEach(async () => { - address = await calculateDepositAddress( - deposit, - BitcoinNetwork.Mainnet, - true - ) + scriptHash = await DepositScript.fromReceipt(deposit, true).getHash() }) - it("should return proper address with prefix bc1", async () => { - // Address is created from same script hash as presented in the witness - // calculateDepositScriptHash scenario i.e. expectedP2WSHDeposit.scriptHash. - // According to https://en.bitcoin.it/wiki/List_of_address_prefixes - // the P2WSH (Bech32) address prefix for mainnet is bc1. - expect(address).to.be.equal(expectedP2WSHDeposit.mainnetAddress) + it("should return proper witness script hash", async () => { + // The script for given deposit should be the same as in + // assembleDepositScript test scenario i.e. expectedDepositScript. + // The hash of this script should correspond to the OP_SHA256 opcode + // which applies SHA-256 on the input. In this case the hash is + // expectedP2WSHDeposit.scriptHash and it can be verified with + // the following command: + // echo -n $SCRIPT | xxd -r -p | openssl dgst -sha256 + expect(scriptHash.toString("hex")).to.be.equal( + expectedP2WSHDeposit.scriptHash + ) }) }) context("when witness option is false", () => { + let scriptHash: Buffer + beforeEach(async () => { - address = await calculateDepositAddress( - deposit, - BitcoinNetwork.Mainnet, - false - ) + scriptHash = await DepositScript.fromReceipt(deposit, false).getHash() }) - it("should return proper address with prefix 3", async () => { - // Address is created from same script hash as presented in the non-witness - // calculateDepositScriptHash scenario i.e. expectedP2SHDeposit.scriptHash. - // According to https://en.bitcoin.it/wiki/List_of_address_prefixes - // the P2SH address prefix for mainnet is 3. - expect(address).to.be.equal(expectedP2SHDeposit.mainnetAddress) + it("should return proper non-witness script hash", async () => { + // The script for given deposit should be the same as in + // assembleDepositScript test scenario i.e. expectedDepositScript. + // The hash of this script should correspond to the OP_HASH160 opcode + // which applies SHA-256 and then RIPEMD-160 on the input. In this case + // the hash is expectedP2SHDeposit.scriptHash and it can be verified + // with the following command: + // echo -n $SCRIPT | xxd -r -p | openssl dgst -sha256 -binary | openssl dgst -rmd160 + expect(scriptHash.toString("hex")).to.be.equal( + expectedP2SHDeposit.scriptHash + ) }) }) }) - context("when network is testnet", () => { - context("when witness option is true", () => { - beforeEach(async () => { - address = await calculateDepositAddress( - deposit, - BitcoinNetwork.Testnet, - true - ) + describe("deriveAddress", () => { + let address: string + + context("when network is mainnet", () => { + context("when witness option is true", () => { + beforeEach(async () => { + address = await DepositScript.fromReceipt( + deposit, + true + ).deriveAddress(BitcoinNetwork.Mainnet) + }) + + it("should return proper address with prefix bc1", async () => { + // Address is created from same script hash as presented in the witness + // DepositScript.getHash scenario i.e. expectedP2WSHDeposit.scriptHash. + // According to https://en.bitcoin.it/wiki/List_of_address_prefixes + // the P2WSH (Bech32) address prefix for mainnet is bc1. + expect(address).to.be.equal(expectedP2WSHDeposit.mainnetAddress) + }) }) - it("should return proper address with prefix tb1", async () => { - // Address is created from same script hash as presented in the witness - // calculateDepositScriptHash scenario i.e. expectedP2WSHDeposit.scriptHash. - // According to https://en.bitcoin.it/wiki/List_of_address_prefixes - // the P2WSH (Bech32) address prefix for testnet is tb1. - expect(address).to.be.equal(expectedP2WSHDeposit.testnetAddress) + context("when witness option is false", () => { + beforeEach(async () => { + address = await DepositScript.fromReceipt( + deposit, + false + ).deriveAddress(BitcoinNetwork.Mainnet) + }) + + it("should return proper address with prefix 3", async () => { + // Address is created from same script hash as presented in the non-witness + // DepositScript.getHash scenario i.e. expectedP2SHDeposit.scriptHash. + // According to https://en.bitcoin.it/wiki/List_of_address_prefixes + // the P2SH address prefix for mainnet is 3. + expect(address).to.be.equal(expectedP2SHDeposit.mainnetAddress) + }) }) }) - context("when witness option is false", () => { - beforeEach(async () => { - address = await calculateDepositAddress( - deposit, - BitcoinNetwork.Testnet, - false - ) + context("when network is testnet", () => { + context("when witness option is true", () => { + beforeEach(async () => { + address = await DepositScript.fromReceipt( + deposit, + true + ).deriveAddress(BitcoinNetwork.Testnet) + }) + + it("should return proper address with prefix tb1", async () => { + // Address is created from same script hash as presented in the witness + // DepositScript.getHash scenario i.e. expectedP2WSHDeposit.scriptHash. + // According to https://en.bitcoin.it/wiki/List_of_address_prefixes + // the P2WSH (Bech32) address prefix for testnet is tb1. + expect(address).to.be.equal(expectedP2WSHDeposit.testnetAddress) + }) }) - it("should return proper address with prefix 2", async () => { - // Address is created from same script hash as presented in the witness - // calculateDepositScriptHash scenario i.e. expectedP2SHDeposit.scriptHash. - // According to https://en.bitcoin.it/wiki/List_of_address_prefixes - // the P2SH address prefix for testnet is 2. - expect(address).to.be.equal(expectedP2SHDeposit.testnetAddress) + context("when witness option is false", () => { + beforeEach(async () => { + address = await DepositScript.fromReceipt( + deposit, + false + ).deriveAddress(BitcoinNetwork.Testnet) + }) + + it("should return proper address with prefix 2", async () => { + // Address is created from same script hash as presented in the witness + // DepositScript.getHash scenario i.e. expectedP2SHDeposit.scriptHash. + // According to https://en.bitcoin.it/wiki/List_of_address_prefixes + // the P2SH address prefix for testnet is 2. + expect(address).to.be.equal(expectedP2SHDeposit.testnetAddress) + }) }) }) }) }) - describe("revealDeposit", () => { - let transaction: BitcoinRawTx - let depositUtxo: BitcoinUtxo - let bitcoinClient: MockBitcoinClient - let bridge: MockBridge - - beforeEach(async () => { - // Create a deposit transaction. - const result = await assembleDepositTransaction( - deposit, - [testnetUTXO], - testnetPrivateKey, - true - ) - - transaction = result.rawTransaction - depositUtxo = result.depositUtxo + describe("Deposit", () => { + describe("", () => { + describe("auto funding outpoint detection mode", () => { + // TODO: Unit test for this case. + }) - // Initialize the mock Bitcoin client to return the raw transaction - // data for the given deposit UTXO. - bitcoinClient = new MockBitcoinClient() - const rawTransactions = new Map() - rawTransactions.set(depositUtxo.transactionHash.toString(), transaction) - bitcoinClient.rawTransactions = rawTransactions + describe("manual funding outpoint provision mode", () => { + let transaction: BitcoinRawTx + let depositUtxo: BitcoinUtxo + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient - // Initialize the mock Bridge. - bridge = new MockBridge() + beforeEach(async () => { + const depositFunding = DepositFunding.fromScript( + DepositScript.fromReceipt(deposit) + ) - await revealDeposit(depositUtxo, deposit, bitcoinClient, bridge) - }) + // Create a deposit transaction. + const result = await depositFunding.assembleTransaction( + depositAmount, + [testnetUTXO], + testnetPrivateKey + ) - it("should reveal the deposit to the Bridge", () => { - expect(bridge.revealDepositLog.length).to.be.equal(1) + transaction = result.rawTransaction + depositUtxo = result.depositUtxo - const revealDepositLogEntry = bridge.revealDepositLog[0] - expect(revealDepositLogEntry.depositTx).to.be.eql( - extractBitcoinRawTxVectors(transaction) - ) - expect(revealDepositLogEntry.depositOutputIndex).to.be.equal(0) - expect(revealDepositLogEntry.deposit).to.be.eql(deposit) - }) - }) + // Initialize the mock Bitcoin client to return the raw transaction + // data for the given deposit UTXO. + bitcoinClient = new MockBitcoinClient() + const rawTransactions = new Map() + rawTransactions.set( + depositUtxo.transactionHash.toString(), + transaction + ) + bitcoinClient.rawTransactions = rawTransactions - describe("getRevealedDeposit", () => { - let depositUtxo: BitcoinUtxo - let revealedDeposit: DepositRequest - let bridge: MockBridge - - beforeEach(async () => { - // Create a deposit transaction. - ;({ depositUtxo } = await assembleDepositTransaction( - deposit, - [testnetUTXO], - testnetPrivateKey, - true - )) - - revealedDeposit = { - depositor: deposit.depositor, - amount: deposit.amount, - vault: EthereumAddress.from("954b98637ca318a4d6e7ca6ffd1690b8e77df637"), - revealedAt: 1654774330, - sweptAt: 1655033516, - treasuryFee: BigNumber.from(200), - } - - const revealedDeposits = new Map() - revealedDeposits.set( - MockBridge.buildDepositKey( - depositUtxo.transactionHash, - depositUtxo.outputIndex - ), - revealedDeposit - ) + // Initialize the mock Bridge. + tbtcContracts = new MockTBTCContracts() - bridge = new MockBridge() - bridge.setDeposits(revealedDeposits) - }) + await ( + await Deposit.fromReceipt(deposit, tbtcContracts, bitcoinClient) + ).initiateMinting(depositUtxo) + }) - it("should return the expected revealed deposit", async () => { - const actualRevealedDeposit = await getRevealedDeposit( - depositUtxo, - bridge - ) + it("should reveal the deposit to the Bridge", () => { + expect(tbtcContracts.bridge.revealDepositLog.length).to.be.equal(1) - expect(actualRevealedDeposit).to.be.eql(revealedDeposit) + const revealDepositLogEntry = tbtcContracts.bridge.revealDepositLog[0] + expect(revealDepositLogEntry.depositTx).to.be.eql( + extractBitcoinRawTxVectors(transaction) + ) + expect(revealDepositLogEntry.depositOutputIndex).to.be.equal(0) + expect(revealDepositLogEntry.deposit).to.be.eql(deposit) + }) + }) }) }) - describe("suggestDepositWallet", () => { - const publicKey = - "03989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9" - - let bridge: MockBridge - - beforeEach(async () => { - bridge = new MockBridge() - bridge.setActiveWalletPublicKey(publicKey) - }) - - it("should return the deposit wallet's public key", async () => { - expect(await suggestDepositWallet(bridge)).to.be.equal(publicKey) - }) + describe("DepositsService", () => { + // TODO: Implement unit tests. }) }) diff --git a/typescript/test/utils/mock-tbtc-contracts.ts b/typescript/test/utils/mock-tbtc-contracts.ts new file mode 100644 index 000000000..5a6568dbb --- /dev/null +++ b/typescript/test/utils/mock-tbtc-contracts.ts @@ -0,0 +1,19 @@ +import { TBTCContracts } from "../../src/lib/contracts" +import { MockBridge } from "./mock-bridge" +import { MockTBTCToken } from "./mock-tbtc-token" +import { MockTBTCVault } from "./mock-tbtc-vault" +import { MockWalletRegistry } from "./mock-wallet-registry" + +export class MockTBTCContracts implements TBTCContracts { + public readonly bridge: MockBridge + public readonly tbtcToken: MockTBTCToken + public readonly tbtcVault: MockTBTCVault + public readonly walletRegistry: MockWalletRegistry + + constructor() { + this.bridge = new MockBridge() + this.tbtcToken = new MockTBTCToken() + this.tbtcVault = new MockTBTCVault() + this.walletRegistry = new MockWalletRegistry() + } +} diff --git a/typescript/test/utils/mock-tbtc-vault.ts b/typescript/test/utils/mock-tbtc-vault.ts new file mode 100644 index 000000000..3b58011e5 --- /dev/null +++ b/typescript/test/utils/mock-tbtc-vault.ts @@ -0,0 +1,83 @@ +import { + TBTCVault, + OptimisticMintingCancelledEvent, + OptimisticMintingFinalizedEvent, + OptimisticMintingRequestedEvent, + ChainIdentifier, + GetChainEvents, + OptimisticMintingRequest, +} from "../../src/lib/contracts" +import { BitcoinTxHash } from "../../src/lib/bitcoin" +import { Hex } from "../../src/lib/utils" +import { EthereumAddress } from "../../src" + +export class MockTBTCVault implements TBTCVault { + getOptimisticMintingCancelledEvents( + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + throw new Error("not implemented") + } + + getOptimisticMintingFinalizedEvents( + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + throw new Error("not implemented") + } + + getOptimisticMintingRequestedEvents( + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + throw new Error("not implemented") + } + + cancelOptimisticMint( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + throw new Error("not implemented") + } + + finalizeOptimisticMint( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + throw new Error("not implemented") + } + + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from("0x594cfd89700040163727828AE20B52099C58F02C") + } + + getMinters(): Promise { + throw new Error("not implemented") + } + + isGuardian(identifier: ChainIdentifier): Promise { + throw new Error("not implemented") + } + + isMinter(identifier: ChainIdentifier): Promise { + throw new Error("not implemented") + } + + optimisticMintingDelay(): Promise { + throw new Error("not implemented") + } + + optimisticMintingRequests( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + throw new Error("not implemented") + } + + requestOptimisticMint( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + throw new Error("not implemented") + } +} diff --git a/typescript/test/utils/mock-wallet-registry.ts b/typescript/test/utils/mock-wallet-registry.ts new file mode 100644 index 000000000..708c6fb00 --- /dev/null +++ b/typescript/test/utils/mock-wallet-registry.ts @@ -0,0 +1,35 @@ +import { + GetChainEvents, + WalletRegistry, + DkgResultApprovedEvent, + DkgResultChallengedEvent, + DkgResultSubmittedEvent, +} from "../../src/lib/contracts" +import { Hex } from "../../src/lib/utils" + +export class MockWalletRegistry implements WalletRegistry { + getDkgResultApprovedEvents( + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + throw new Error("not implemented") + } + + getDkgResultChallengedEvents( + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + throw new Error("not implemented") + } + + getDkgResultSubmittedEvents( + options?: GetChainEvents.Options, + ...filterArgs: Array + ): Promise { + throw new Error("not implemented") + } + + getWalletPublicKey(walletID: Hex): Promise { + throw new Error("not implemented") + } +} From 40b990c87cd641f80dcae4d87f59bcb5934c64f2 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 28 Sep 2023 15:56:39 +0200 Subject: [PATCH 17/43] Move `deposit-refund.ts` to the `./services/deposits` feature module This code is part of the user-facing deposit flow so should live in the `./services/deposits` module. --- typescript/src/deposit-refund.ts | 301 --------------------- typescript/src/services/deposits/refund.ts | 301 +++++++++++++++++++++ typescript/test/deposit-refund.test.ts | 280 ++++++++++--------- 3 files changed, 449 insertions(+), 433 deletions(-) delete mode 100644 typescript/src/deposit-refund.ts create mode 100644 typescript/src/services/deposits/refund.ts diff --git a/typescript/src/deposit-refund.ts b/typescript/src/deposit-refund.ts deleted file mode 100644 index 952516e9a..000000000 --- a/typescript/src/deposit-refund.ts +++ /dev/null @@ -1,301 +0,0 @@ -import bcoin from "bcoin" -import { BigNumber } from "ethers" -import { - BitcoinPrivateKeyUtils, - BitcoinRawTx, - BitcoinClient, - BitcoinTxHash, - BitcoinUtxo, - BitcoinHashUtils, - BitcoinPublicKeyUtils, -} from "./lib/bitcoin" -import { DepositReceipt, validateDepositReceipt } from "./lib/contracts" -import { DepositScript } from "./services/deposits" - -/** - * Submits a deposit refund by creating and broadcasting a Bitcoin P2(W)PKH - * deposit refund transaction. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @param fee - the value that will be subtracted from the deposit UTXO being - * refunded and used as the transaction fee. - * @param utxo - UTXO that was created during depositing that needs be refunded. - * @param deposit - Details of the deposit being refunded. It should contain - * the same data that was used during depositing. - * @param refunderAddress - Recipient Bitcoin wallet address of the refunded - * deposit. - * @param refunderPrivateKey - Bitcoin wallet private key of the refunder. - * It must correspond to the `refundPublicKeyHash` of the deposit script. - * @returns The outcome is the deposit refund transaction hash. - * @dev This function should be called by the refunder after `refundLocktime` - * passes plus 1 hour. The additional hour of waiting is the result of - * adopting BIP113 which compares the transaction's locktime against the - * median timestamp of the last 11 blocks. This median time lags - * the current unix time by about 1 hour. - */ -export async function submitDepositRefundTransaction( - bitcoinClient: BitcoinClient, - fee: BigNumber, - utxo: BitcoinUtxo, - deposit: DepositReceipt, - refunderAddress: string, - refunderPrivateKey: string -): Promise<{ transactionHash: BitcoinTxHash }> { - const utxoRawTransaction = await bitcoinClient.getRawTransaction( - utxo.transactionHash - ) - - const utxoWithRaw = { - ...utxo, - transactionHex: utxoRawTransaction.transactionHex, - } - - const { transactionHash, rawTransaction } = - await assembleDepositRefundTransaction( - fee, - utxoWithRaw, - deposit, - refunderAddress, - refunderPrivateKey - ) - - // Note that `broadcast` may fail silently (i.e. no error will be returned, - // even if the transaction is rejected by other nodes and does not enter the - // mempool, for example due to an UTXO being already spent). - await bitcoinClient.broadcast(rawTransaction) - - return { transactionHash } -} - -/** - * Assembles a Bitcoin P2(W)PKH deposit refund transaction. - * @param fee - the value that will be subtracted from the deposit UTXO being - * refunded and used as the transaction fee. - * @param utxo - UTXO that was created during depositing that needs be refunded. - * @param deposit - Details of the deposit being refunded. It should contain - * the same data that was used during depositing. - * @param refunderAddress - Recipient Bitcoin wallet address of the refunded - * deposit. - * @param refunderPrivateKey - Bitcoin wallet private key of the refunder. - * It must correspond to the `refundPublicKeyHash` of the deposit script. - * @returns The outcome consisting of: - * - the deposit refund transaction hash, - * - the refund transaction in the raw format. - */ -export async function assembleDepositRefundTransaction( - fee: BigNumber, - utxo: BitcoinUtxo & BitcoinRawTx, - deposit: DepositReceipt, - refunderAddress: string, - refunderPrivateKey: string -): Promise<{ - transactionHash: BitcoinTxHash - rawTransaction: BitcoinRawTx -}> { - validateDepositReceipt(deposit) - - const refunderKeyRing = - BitcoinPrivateKeyUtils.createKeyRing(refunderPrivateKey) - - const transaction = new bcoin.MTX() - - transaction.addOutput({ - script: bcoin.Script.fromAddress(refunderAddress), - value: utxo.value.toNumber(), - }) - - const inputCoin = bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), - utxo.outputIndex, - -1 - ) - - await transaction.fund([inputCoin], { - changeAddress: refunderAddress, - hardFee: fee.toNumber(), - subtractFee: true, - }) - - if (transaction.outputs.length != 1) { - throw new Error("Deposit refund transaction must have only one output") - } - - // In order to be able to spend the UTXO being refunded the transaction's - // locktime must be set to a value equal to or higher than the refund locktime. - // Additionally, the input's sequence must be set to a value different than - // `0xffffffff`. These requirements are the result of BIP-65. - transaction.locktime = locktimeToUnixTimestamp(deposit.refundLocktime) - transaction.inputs[0].sequence = 0xfffffffe - - // Sign the input - const previousOutpoint = transaction.inputs[0].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - const previousScript = previousOutput.script - - if (previousScript.isScripthash()) { - // P2SH UTXO deposit input - await signP2SHDepositInput(transaction, 0, deposit, refunderKeyRing) - } else if (previousScript.isWitnessScripthash()) { - // P2WSH UTXO deposit input - await signP2WSHDepositInput(transaction, 0, deposit, refunderKeyRing) - } else { - throw new Error("Unsupported UTXO script type") - } - - // Verify the transaction by executing its input scripts. - const tx = transaction.toTX() - if (!tx.verify(transaction.view)) { - throw new Error("Transaction verification failure") - } - - const transactionHash = BitcoinTxHash.from(transaction.txid()) - - return { - transactionHash, - rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), - }, - } -} - -/** - * Creates data needed to sign a deposit input to be refunded. - * @param transaction - Mutable transaction containing the input to be refunded. - * @param inputIndex - Index that points to the input. - * @param deposit - Data of the deposit to be refunded. - * @param refunderKeyRing - Key ring created using the refunder's private key. - * @returns Data needed to sign the input. - */ -async function prepareInputSignData( - transaction: any, - inputIndex: number, - deposit: DepositReceipt, - refunderKeyRing: any -): Promise<{ - refunderPublicKey: string - depositScript: any - previousOutputValue: number -}> { - const previousOutpoint = transaction.inputs[inputIndex].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - - const refunderPublicKey = refunderKeyRing.getPublicKey("hex") - if ( - BitcoinHashUtils.computeHash160(refunderKeyRing.getPublicKey("hex")) != - deposit.refundPublicKeyHash - ) { - throw new Error( - "Refund public key does not correspond to the refunder private key" - ) - } - - if (!BitcoinPublicKeyUtils.isCompressedPublicKey(refunderPublicKey)) { - throw new Error("Refunder public key must be compressed") - } - - const depositScript = bcoin.Script.fromRaw( - Buffer.from(await DepositScript.fromReceipt(deposit).getPlainText(), "hex") - ) - - return { - refunderPublicKey: refunderPublicKey, - depositScript: depositScript, - previousOutputValue: previousOutput.value, - } -} - -/** - * Creates and sets `scriptSig` for the transaction input at the given index by - * combining signature, refunder's public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param deposit - Data of the deposit. - * @param refunderKeyRing - Key ring created using the refunder's private key. - * @returns Empty return. - */ -async function signP2SHDepositInput( - transaction: any, - inputIndex: number, - deposit: DepositReceipt, - refunderKeyRing: any -) { - const { refunderPublicKey, depositScript, previousOutputValue } = - await prepareInputSignData( - transaction, - inputIndex, - deposit, - refunderKeyRing - ) - - const signature: Buffer = transaction.signature( - inputIndex, - depositScript, - previousOutputValue, - refunderKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 0 // legacy sighash version - ) - const scriptSig = new bcoin.Script() - scriptSig.clear() - scriptSig.pushData(signature) - scriptSig.pushData(Buffer.from(refunderPublicKey, "hex")) - scriptSig.pushData(depositScript.toRaw()) - scriptSig.compile() - - transaction.inputs[inputIndex].script = scriptSig -} - -/** - * Creates and sets witness script for the transaction input at the given index - * by combining signature, refunder public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param deposit - Data of the deposit. - * @param refunderKeyRing - Key ring created using the refunder's private key. - * @returns Empty return. - */ -async function signP2WSHDepositInput( - transaction: any, - inputIndex: number, - deposit: DepositReceipt, - refunderKeyRing: any -) { - const { refunderPublicKey, depositScript, previousOutputValue } = - await prepareInputSignData( - transaction, - inputIndex, - deposit, - refunderKeyRing - ) - - const signature: Buffer = transaction.signature( - inputIndex, - depositScript, - previousOutputValue, - refunderKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 1 // segwit sighash version - ) - - const witness = new bcoin.Witness() - witness.clear() - witness.pushData(signature) - witness.pushData(Buffer.from(refunderPublicKey, "hex")) - witness.pushData(depositScript.toRaw()) - witness.compile() - - transaction.inputs[inputIndex].witness = witness -} - -/** - * Converts locktime from the little endian hexstring format to the Unix - * timestamp. - * @param locktime - Locktime as a little endian hexstring. - * @returns Locktime as a Unix timestamp. - */ -function locktimeToUnixTimestamp(locktime: string): number { - const bigEndianLocktime = Buffer.from(locktime, "hex") - .reverse() - .toString("hex") - - return parseInt(bigEndianLocktime, 16) -} diff --git a/typescript/src/services/deposits/refund.ts b/typescript/src/services/deposits/refund.ts new file mode 100644 index 000000000..4582c7f1c --- /dev/null +++ b/typescript/src/services/deposits/refund.ts @@ -0,0 +1,301 @@ +import bcoin from "bcoin" +import { BigNumber } from "ethers" +import { + BitcoinPrivateKeyUtils, + BitcoinRawTx, + BitcoinClient, + BitcoinTxHash, + BitcoinUtxo, + BitcoinHashUtils, + BitcoinPublicKeyUtils, +} from "../../lib/bitcoin" +import { validateDepositReceipt } from "../../lib/contracts" +import { DepositScript } from "./" + +/** + * Component allowing to craft and submit the Bitcoin refund transaction using + * the given tBTC v2 deposit script. + * + * @experimental THIS IS EXPERIMENTAL CODE THAT CAN BE CHANGED OR REMOVED + * IN FUTURE RELEASES. IT SHOULD BE USED ONLY FOR INTERNAL + * PURPOSES AND EXTERNAL APPLICATIONS SHOULD NOT DEPEND ON IT. + */ +// TODO: Abstract away transaction signing so there is no need to deal with +// private key directly. +export class DepositRefund { + public readonly script: DepositScript + + private constructor(script: DepositScript) { + this.script = script + } + + static fromScript(script: DepositScript): DepositRefund { + return new DepositRefund(script) + } + + /** + * Submits a deposit refund by creating and broadcasting a Bitcoin P2(W)PKH + * deposit refund transaction. + * @param bitcoinClient - Bitcoin client used to interact with the network. + * @param fee - the value that will be subtracted from the deposit UTXO being + * refunded and used as the transaction fee. + * @param utxo - UTXO that was created during depositing that needs be refunded. + * @param refunderAddress - Recipient Bitcoin wallet address of the refunded + * deposit. + * @param refunderPrivateKey - Bitcoin wallet private key of the refunder. + * It must correspond to the `refundPublicKeyHash` of the deposit script. + * @returns The outcome is the deposit refund transaction hash. + * @dev This function should be called by the refunder after `refundLocktime` + * passes plus 1 hour. The additional hour of waiting is the result of + * adopting BIP113 which compares the transaction's locktime against the + * median timestamp of the last 11 blocks. This median time lags + * the current unix time by about 1 hour. + */ + async submitTransaction( + bitcoinClient: BitcoinClient, + fee: BigNumber, + utxo: BitcoinUtxo, + refunderAddress: string, + refunderPrivateKey: string + ): Promise<{ transactionHash: BitcoinTxHash }> { + const utxoRawTransaction = await bitcoinClient.getRawTransaction( + utxo.transactionHash + ) + + const utxoWithRaw = { + ...utxo, + transactionHex: utxoRawTransaction.transactionHex, + } + + const { transactionHash, rawTransaction } = await this.assembleTransaction( + fee, + utxoWithRaw, + refunderAddress, + refunderPrivateKey + ) + + // Note that `broadcast` may fail silently (i.e. no error will be returned, + // even if the transaction is rejected by other nodes and does not enter the + // mempool, for example due to an UTXO being already spent). + await bitcoinClient.broadcast(rawTransaction) + + return { transactionHash } + } + + /** + * Assembles a Bitcoin P2(W)PKH deposit refund transaction. + * @param fee - the value that will be subtracted from the deposit UTXO being + * refunded and used as the transaction fee. + * @param utxo - UTXO that was created during depositing that needs be refunded. + * @param refunderAddress - Recipient Bitcoin wallet address of the refunded + * deposit. + * @param refunderPrivateKey - Bitcoin wallet private key of the refunder. + * It must correspond to the `refundPublicKeyHash` of the deposit script. + * @returns The outcome consisting of: + * - the deposit refund transaction hash, + * - the refund transaction in the raw format. + */ + async assembleTransaction( + fee: BigNumber, + utxo: BitcoinUtxo & BitcoinRawTx, + refunderAddress: string, + refunderPrivateKey: string + ): Promise<{ + transactionHash: BitcoinTxHash + rawTransaction: BitcoinRawTx + }> { + validateDepositReceipt(this.script.receipt) + + const refunderKeyRing = + BitcoinPrivateKeyUtils.createKeyRing(refunderPrivateKey) + + const transaction = new bcoin.MTX() + + transaction.addOutput({ + script: bcoin.Script.fromAddress(refunderAddress), + value: utxo.value.toNumber(), + }) + + const inputCoin = bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), + utxo.outputIndex, + -1 + ) + + await transaction.fund([inputCoin], { + changeAddress: refunderAddress, + hardFee: fee.toNumber(), + subtractFee: true, + }) + + if (transaction.outputs.length != 1) { + throw new Error("Deposit refund transaction must have only one output") + } + + // In order to be able to spend the UTXO being refunded the transaction's + // locktime must be set to a value equal to or higher than the refund locktime. + // Additionally, the input's sequence must be set to a value different than + // `0xffffffff`. These requirements are the result of BIP-65. + transaction.locktime = locktimeToUnixTimestamp( + this.script.receipt.refundLocktime + ) + transaction.inputs[0].sequence = 0xfffffffe + + // Sign the input + const previousOutpoint = transaction.inputs[0].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + const previousScript = previousOutput.script + + if (previousScript.isScripthash()) { + // P2SH UTXO deposit input + await this.signP2SHDepositInput(transaction, 0, refunderKeyRing) + } else if (previousScript.isWitnessScripthash()) { + // P2WSH UTXO deposit input + await this.signP2WSHDepositInput(transaction, 0, refunderKeyRing) + } else { + throw new Error("Unsupported UTXO script type") + } + + // Verify the transaction by executing its input scripts. + const tx = transaction.toTX() + if (!tx.verify(transaction.view)) { + throw new Error("Transaction verification failure") + } + + const transactionHash = BitcoinTxHash.from(transaction.txid()) + + return { + transactionHash, + rawTransaction: { + transactionHex: transaction.toRaw().toString("hex"), + }, + } + } + + /** + * Creates data needed to sign a deposit input to be refunded. + * @param transaction - Mutable transaction containing the input to be refunded. + * @param inputIndex - Index that points to the input. + * @param refunderKeyRing - Key ring created using the refunder's private key. + * @returns Data needed to sign the input. + */ + private async prepareInputSignData( + transaction: any, + inputIndex: number, + refunderKeyRing: any + ): Promise<{ + refunderPublicKey: string + depositScript: any + previousOutputValue: number + }> { + const previousOutpoint = transaction.inputs[inputIndex].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + + const refunderPublicKey = refunderKeyRing.getPublicKey("hex") + if ( + BitcoinHashUtils.computeHash160(refunderKeyRing.getPublicKey("hex")) != + this.script.receipt.refundPublicKeyHash + ) { + throw new Error( + "Refund public key does not correspond to the refunder private key" + ) + } + + if (!BitcoinPublicKeyUtils.isCompressedPublicKey(refunderPublicKey)) { + throw new Error("Refunder public key must be compressed") + } + + const depositScript = bcoin.Script.fromRaw( + Buffer.from(await this.script.getPlainText(), "hex") + ) + + return { + refunderPublicKey: refunderPublicKey, + depositScript: depositScript, + previousOutputValue: previousOutput.value, + } + } + + /** + * Creates and sets `scriptSig` for the transaction input at the given index by + * combining signature, refunder's public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param refunderKeyRing - Key ring created using the refunder's private key. + * @returns Empty return. + */ + private async signP2SHDepositInput( + transaction: any, + inputIndex: number, + refunderKeyRing: any + ) { + const { refunderPublicKey, depositScript, previousOutputValue } = + await this.prepareInputSignData(transaction, inputIndex, refunderKeyRing) + + const signature: Buffer = transaction.signature( + inputIndex, + depositScript, + previousOutputValue, + refunderKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 0 // legacy sighash version + ) + const scriptSig = new bcoin.Script() + scriptSig.clear() + scriptSig.pushData(signature) + scriptSig.pushData(Buffer.from(refunderPublicKey, "hex")) + scriptSig.pushData(depositScript.toRaw()) + scriptSig.compile() + + transaction.inputs[inputIndex].script = scriptSig + } + + /** + * Creates and sets witness script for the transaction input at the given index + * by combining signature, refunder public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param refunderKeyRing - Key ring created using the refunder's private key. + * @returns Empty return. + */ + private async signP2WSHDepositInput( + transaction: any, + inputIndex: number, + refunderKeyRing: any + ) { + const { refunderPublicKey, depositScript, previousOutputValue } = + await this.prepareInputSignData(transaction, inputIndex, refunderKeyRing) + + const signature: Buffer = transaction.signature( + inputIndex, + depositScript, + previousOutputValue, + refunderKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 1 // segwit sighash version + ) + + const witness = new bcoin.Witness() + witness.clear() + witness.pushData(signature) + witness.pushData(Buffer.from(refunderPublicKey, "hex")) + witness.pushData(depositScript.toRaw()) + witness.compile() + + transaction.inputs[inputIndex].witness = witness + } +} + +/** + * Converts locktime from the little endian hexstring format to the Unix + * timestamp. + * @param locktime - Locktime as a little endian hexstring. + * @returns Locktime as a Unix timestamp. + */ +function locktimeToUnixTimestamp(locktime: string): number { + const bigEndianLocktime = Buffer.from(locktime, "hex") + .reverse() + .toString("hex") + + return parseInt(bigEndianLocktime, 16) +} diff --git a/typescript/test/deposit-refund.test.ts b/typescript/test/deposit-refund.test.ts index bafc68736..6825e6fa8 100644 --- a/typescript/test/deposit-refund.test.ts +++ b/typescript/test/deposit-refund.test.ts @@ -1,10 +1,10 @@ import { BigNumber } from "ethers" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" +import bcoin from "bcoin" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) import { expect } from "chai" -import { submitDepositRefundTransaction } from "../src/deposit-refund" import { BitcoinTxHash, BitcoinRawTx } from "./lib/bitcoin" import { refunderPrivateKey, @@ -12,156 +12,172 @@ import { depositRefundOfNonWitnessDepositAndWitnessRefunderAddress, depositRefundOfWitnessDepositAndNonWitnessRefunderAddress, } from "./data/deposit-refund" +import { DepositRefund } from "../src/services/deposits/refund" +import { DepositScript } from "../src/services/deposits" describe("Refund", () => { const fee = BigNumber.from(1520) - describe("submitDepositRefundTransaction", () => { - let bitcoinClient: MockBitcoinClient + describe("DepositRefund", () => { + describe("submitTransaction", () => { + let bitcoinClient: MockBitcoinClient - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - }) + beforeEach(async () => { + bcoin.set("testnet") + + bitcoinClient = new MockBitcoinClient() + }) - context("when the refund transaction is requested to be witness", () => { - context("when the refunded deposit was witness", () => { - let transactionHash: BitcoinTxHash - - beforeEach(async () => { - const utxo = - depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit.utxo - const deposit = - depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit.data - const refunderAddress = - depositRefundOfWitnessDepositAndWitnessRefunderAddress.refunderAddress - const refunderPrivateKey = - "cTWhf1nXc7aW8BN2qLtWcPtcgcWYKfzRXkCJNsuQ86HR8uJBYfMc" - - const rawTransactions = new Map() - rawTransactions.set(utxo.transactionHash.toString(), { - transactionHex: utxo.transactionHex, + context("when the refund transaction is requested to be witness", () => { + context("when the refunded deposit was witness", () => { + let transactionHash: BitcoinTxHash + + beforeEach(async () => { + const utxo = + depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit + .utxo + const deposit = + depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit + .data + const refunderAddress = + depositRefundOfWitnessDepositAndWitnessRefunderAddress.refunderAddress + const refunderPrivateKey = + "cTWhf1nXc7aW8BN2qLtWcPtcgcWYKfzRXkCJNsuQ86HR8uJBYfMc" + + const rawTransactions = new Map() + rawTransactions.set(utxo.transactionHash.toString(), { + transactionHex: utxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + + const depositRefund = DepositRefund.fromScript( + DepositScript.fromReceipt(deposit) + ) + + ;({ transactionHash } = await depositRefund.submitTransaction( + bitcoinClient, + fee, + utxo, + refunderAddress, + refunderPrivateKey + )) }) - bitcoinClient.rawTransactions = rawTransactions - ;({ transactionHash } = await submitDepositRefundTransaction( - bitcoinClient, - fee, - utxo, - deposit, - refunderAddress, - refunderPrivateKey - )) - }) - it("should broadcast refund transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositRefundOfWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transaction - ) - }) + it("should broadcast refund transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositRefundOfWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transaction + ) + }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositRefundOfWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transactionHash - ) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositRefundOfWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transactionHash + ) + }) }) - }) - context("when the refunded deposit was non-witness", () => { - let transactionHash: BitcoinTxHash - - beforeEach(async () => { - const utxo = - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.deposit - .utxo - const deposit = - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.deposit - .data - const refunderAddress = - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.refunderAddress - - const rawTransactions = new Map() - rawTransactions.set(utxo.transactionHash.toString(), { - transactionHex: utxo.transactionHex, + context("when the refunded deposit was non-witness", () => { + let transactionHash: BitcoinTxHash + + beforeEach(async () => { + const utxo = + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.deposit + .utxo + const deposit = + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.deposit + .data + const refunderAddress = + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.refunderAddress + + const rawTransactions = new Map() + rawTransactions.set(utxo.transactionHash.toString(), { + transactionHex: utxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + + const depositRefund = DepositRefund.fromScript( + DepositScript.fromReceipt(deposit) + ) + + ;({ transactionHash } = await depositRefund.submitTransaction( + bitcoinClient, + fee, + utxo, + refunderAddress, + refunderPrivateKey + )) }) - bitcoinClient.rawTransactions = rawTransactions - ;({ transactionHash } = await submitDepositRefundTransaction( - bitcoinClient, - fee, - utxo, - deposit, - refunderAddress, - refunderPrivateKey - )) - }) - it("should broadcast refund transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transaction - ) - }) + it("should broadcast refund transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transaction + ) + }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transactionHash - ) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transactionHash + ) + }) }) }) - }) - context( - "when the refund transaction is requested to be non-witness", - () => { - let transactionHash: BitcoinTxHash - - beforeEach(async () => { - const utxo = - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.deposit - .utxo - const deposit = - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.deposit - .data - const refunderAddress = - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.refunderAddress - - const rawTransactions = new Map() - rawTransactions.set(utxo.transactionHash.toString(), { - transactionHex: utxo.transactionHex, + context( + "when the refund transaction is requested to be non-witness", + () => { + let transactionHash: BitcoinTxHash + + beforeEach(async () => { + const utxo = + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.deposit + .utxo + const deposit = + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.deposit + .data + const refunderAddress = + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.refunderAddress + + const rawTransactions = new Map() + rawTransactions.set(utxo.transactionHash.toString(), { + transactionHex: utxo.transactionHex, + }) + + const depositRefund = DepositRefund.fromScript( + DepositScript.fromReceipt(deposit) + ) + + bitcoinClient.rawTransactions = rawTransactions + ;({ transactionHash } = await depositRefund.submitTransaction( + bitcoinClient, + fee, + utxo, + refunderAddress, + refunderPrivateKey + )) }) - bitcoinClient.rawTransactions = rawTransactions - ;({ transactionHash } = await submitDepositRefundTransaction( - bitcoinClient, - fee, - utxo, - deposit, - refunderAddress, - refunderPrivateKey - )) - }) - - it("should broadcast refund transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress - .expectedRefund.transaction - ) - }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress - .expectedRefund.transactionHash - ) - }) - } - ) - }) + it("should broadcast refund transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress + .expectedRefund.transaction + ) + }) - describe("assembleDepositRefundTransaction", () => { - // TODO + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress + .expectedRefund.transactionHash + ) + }) + } + ) + }) }) }) From 8ec25d6ac33cbbd2fd7542f92c1f90287948a234 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 29 Sep 2023 09:04:02 +0200 Subject: [PATCH 18/43] Add some TODOs around deposits --- typescript/src/services/deposits/deposit.ts | 1 + typescript/src/services/deposits/deposits-service.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/typescript/src/services/deposits/deposit.ts b/typescript/src/services/deposits/deposit.ts index 4ce3099a3..e455724ad 100644 --- a/typescript/src/services/deposits/deposit.ts +++ b/typescript/src/services/deposits/deposit.ts @@ -111,6 +111,7 @@ export class Deposit { * initiate minting (both modes). */ // TODO: Cover auto funding outpoint detection with unit tests. + // TODO: Return Hex. async initiateMinting(fundingOutpoint?: BitcoinTxOutpoint): Promise { let resolvedFundingOutpoint: BitcoinTxOutpoint diff --git a/typescript/src/services/deposits/deposits-service.ts b/typescript/src/services/deposits/deposits-service.ts index 8b6a2e3be..d7c5c73b5 100644 --- a/typescript/src/services/deposits/deposits-service.ts +++ b/typescript/src/services/deposits/deposits-service.ts @@ -75,6 +75,9 @@ export class DepositsService { const walletPublicKeyHash = BitcoinHashUtils.computeHash160(walletPublicKey) + // TODO: Only P2(W)PKH addresses can be used for recovery. The below conversion + // function ensures that but it would be good to check it here as well + // in case the converter implementation changes. const refundPublicKeyHash = BitcoinAddressConverter.addressToPublicKeyHash( bitcoinRecoveryAddress ) From 14ecaef2e7a6a05b19550beb46dc935651bab219 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 29 Sep 2023 11:54:28 +0200 Subject: [PATCH 19/43] Move part of `redemption.ts` to the `./services/redemptions` feature module Move the user-facing redemption code to the new `./services/redemptions` module. Some refactoring meant to facilitate usage was done as well. --- typescript/src/index.ts | 13 +- typescript/src/redemption.ts | 291 +--- typescript/src/services/redemptions/index.ts | 1 + .../redemptions/redemptions-service.ts | 363 +++++ typescript/src/wallet.ts | 124 -- typescript/test/redemption.test.ts | 1172 +++++++++++------ typescript/test/wallet.test.ts | 291 ---- 7 files changed, 1170 insertions(+), 1085 deletions(-) create mode 100644 typescript/src/services/redemptions/index.ts create mode 100644 typescript/src/services/redemptions/redemptions-service.ts delete mode 100644 typescript/src/wallet.ts delete mode 100644 typescript/test/wallet.test.ts diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 84130baa0..1c50e4168 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -4,12 +4,7 @@ import { validateBitcoinSpvProof } from "./lib/bitcoin" import { submitDepositSweepProof } from "./deposit-sweep" -import { - requestRedemption, - submitRedemptionProof, - getRedemptionRequest, - findWalletForRedemption, -} from "./redemption" +import { submitRedemptionProof } from "./redemption" import { requestOptimisticMint, @@ -18,12 +13,6 @@ import { getOptimisticMintingRequest, } from "./optimistic-minting" -export const TBTC = { - requestRedemption, - getRedemptionRequest, - findWalletForRedemption, -} - export const SpvMaintainer = { submitDepositSweepProof, submitRedemptionProof, diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts index cda5658f2..5c1696812 100644 --- a/typescript/src/redemption.ts +++ b/typescript/src/redemption.ts @@ -2,7 +2,6 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { assembleBitcoinSpvProof, - BitcoinNetwork, BitcoinPrivateKeyUtils, extractBitcoinRawTxVectors, BitcoinRawTx, @@ -10,42 +9,7 @@ import { BitcoinClient, BitcoinTxHash, } from "./lib/bitcoin" -import { - Bridge, - RedemptionRequest, - TBTCToken, - WalletState, -} from "./lib/contracts" -import { determineWalletMainUtxo } from "./wallet" -import { Hex } from "./lib/utils" - -/** - * Requests a redemption of tBTC into BTC. - * @param walletPublicKey - The Bitcoin public key of the wallet. Must be in the - * compressed form (33 bytes long with 02 or 03 prefix). - * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO held - * by the on-chain Bridge contract. - * @param redeemerOutputScript - The output script that the redeemed funds will - * be locked to. Must be un-prefixed and not prepended with length. - * @param amount - The amount to be redeemed with the precision of the tBTC - * on-chain token contract. - * @param tBTCToken - Handle to the TBTCToken on-chain contract. - * @returns Transaction hash of the request redemption transaction. - */ -export async function requestRedemption( - walletPublicKey: string, - mainUtxo: BitcoinUtxo, - redeemerOutputScript: string, - amount: BigNumber, - tBTCToken: TBTCToken -): Promise { - return await tBTCToken.requestRedemption( - walletPublicKey, - mainUtxo, - redeemerOutputScript, - amount - ) -} +import { Bridge, RedemptionRequest } from "./lib/contracts" /** * Handles pending redemption requests by creating a redemption transaction @@ -89,14 +53,27 @@ export async function submitRedemptionTransaction( transactionHex: mainUtxoRawTransaction.transactionHex, } - const redemptionRequests = await getWalletRedemptionRequests( - bridge, - BitcoinPrivateKeyUtils.createKeyRing(walletPrivateKey) - .getPublicKey() - .toString("hex"), - redeemerOutputScripts, - "pending" - ) + const walletPublicKey = BitcoinPrivateKeyUtils.createKeyRing(walletPrivateKey) + .getPublicKey() + .toString("hex") + + const redemptionRequests: RedemptionRequest[] = [] + + for (const redeemerOutputScript of redeemerOutputScripts) { + const redemptionRequest = await bridge.pendingRedemptions( + walletPublicKey, + redeemerOutputScript + ) + + if (redemptionRequest.requestedAt == 0) { + throw new Error("Redemption request does not exist") + } + + redemptionRequests.push({ + ...redemptionRequest, + redeemerOutputScript: redeemerOutputScript, + }) + } const { transactionHash, newMainUtxo, rawTransaction } = await assembleRedemptionTransaction( @@ -114,74 +91,6 @@ export async function submitRedemptionTransaction( return { transactionHash, newMainUtxo } } -/** - * Gets a list of wallet's redemption requests from the provided Bridge - * on-chain contract handle. - * @dev It is up to the caller of this function to ensure that each of the - * redeemer output scripts represents a valid redemption request - * in the Bridge on-chain contract. An exception will be thrown if any of - * the redeemer output scripts (along with the wallet public key - * corresponding to the provided private key) does not represent a valid - * redemption request. - * @param bridge - The handle to the Bridge on-chain contract - * @param walletPublicKey - Bitcoin public key of the wallet. Must be in the - * compressed form (33 bytes long with 02 or 03 prefix). - * @param redeemerOutputScripts - The list of output scripts that the redeemed - * funds are locked to. The output scripts must be un-prefixed and - * not prepended with length - * @param type Type of redemption requests the function will look for. Can be - * either `pending` or `timedOut`. - * @returns The list of redemption requests. - */ -async function getWalletRedemptionRequests( - bridge: Bridge, - walletPublicKey: string, - redeemerOutputScripts: string[], - type: "pending" | "timedOut" -): Promise { - const redemptionRequests: RedemptionRequest[] = [] - - for (const redeemerOutputScript of redeemerOutputScripts) { - let redemptionRequest: RedemptionRequest - - switch (type) { - case "pending": { - redemptionRequest = await bridge.pendingRedemptions( - walletPublicKey, - redeemerOutputScript - ) - break - } - case "timedOut": { - redemptionRequest = await bridge.timedOutRedemptions( - walletPublicKey, - redeemerOutputScript - ) - break - } - default: { - throw new Error("Unsupported redemption request type") - } - } - - if (redemptionRequest.requestedAt == 0) { - // The requested redemption does not exist among `pendingRedemptions` - // in the Bridge. - throw new Error( - "Provided redeemer output script and wallet public key do not identify a redemption request" - ) - } - - // Redemption exists in the Bridge. Add it to the list. - redemptionRequests.push({ - ...redemptionRequest, - redeemerOutputScript: redeemerOutputScript, - }) - } - - return redemptionRequests -} - /** * Assembles a Bitcoin redemption transaction. * The transaction will have a single input (main UTXO of the wallet making @@ -345,159 +254,3 @@ export async function submitRedemptionProof( walletPublicKey ) } - -/** - * Gets a redemption request from the bridge. - * @param walletPublicKey Bitcoin public key of the wallet the request is - * targeted to. Must be in the compressed form (33 bytes long with 02 - * or 03 prefix). - * @param redeemerOutputScript The redeemer output script the redeemed funds - * are supposed to be locked on. Must be un-prefixed and not prepended - * with length. - * @param type Type of the redemption request the function will look for. Can be - * either `pending` or `timedOut`. - * @param bridge The handle to the Bridge on-chain contract - * @returns The resulting redemption request. - */ -export async function getRedemptionRequest( - walletPublicKey: string, - redeemerOutputScript: string, - type: "pending" | "timedOut", - bridge: Bridge -): Promise { - const redemptionRequests = await getWalletRedemptionRequests( - bridge, - walletPublicKey, - [redeemerOutputScript], - type - ) - - if (redemptionRequests.length != 1) { - throw new Error(`Returned an incorrect number of redemption requests`) - } - - return redemptionRequests[0] -} - -/** - * Finds the oldest live wallet that has enough BTC to handle a redemption - * request. - * @param amount The amount to be redeemed in satoshis. - * @param redeemerOutputScript The redeemer output script the redeemed funds are - * supposed to be locked on. Must be un-prefixed and not prepended with - * length. - * @param bitcoinNetwork Bitcoin network. - * @param bridge The handle to the Bridge on-chain contract. - * @param bitcoinClient Bitcoin client used to interact with the network. - * @returns Promise with the wallet details needed to request a redemption. - */ -export async function findWalletForRedemption( - amount: BigNumber, - redeemerOutputScript: string, - bitcoinNetwork: BitcoinNetwork, - bridge: Bridge, - bitcoinClient: BitcoinClient -): Promise<{ - walletPublicKey: string - mainUtxo: BitcoinUtxo -}> { - const wallets = await bridge.getNewWalletRegisteredEvents() - - let walletData: - | { - walletPublicKey: string - mainUtxo: BitcoinUtxo - } - | undefined = undefined - let maxAmount = BigNumber.from(0) - let liveWalletsCounter = 0 - - for (const wallet of wallets) { - const { walletPublicKeyHash } = wallet - const { state, walletPublicKey, pendingRedemptionsValue } = - await bridge.wallets(walletPublicKeyHash) - - // Wallet must be in Live state. - if (state !== WalletState.Live) { - console.debug( - `Wallet is not in Live state ` + - `(wallet public key hash: ${walletPublicKeyHash.toString()}). ` + - `Continue the loop execution to the next wallet...` - ) - continue - } - liveWalletsCounter++ - - // Wallet must have a main UTXO that can be determined. - const mainUtxo = await determineWalletMainUtxo( - walletPublicKeyHash, - bridge, - bitcoinClient, - bitcoinNetwork - ) - if (!mainUtxo) { - console.debug( - `Could not find matching UTXO on chains ` + - `for wallet public key hash (${walletPublicKeyHash.toString()}). ` + - `Continue the loop execution to the next wallet...` - ) - continue - } - - const pendingRedemption = await bridge.pendingRedemptions( - walletPublicKey.toString(), - redeemerOutputScript - ) - - if (pendingRedemption.requestedAt != 0) { - console.debug( - `There is a pending redemption request from this wallet to the ` + - `same Bitcoin address. Given wallet public key hash` + - `(${walletPublicKeyHash.toString()}) and redeemer output script ` + - `(${redeemerOutputScript}) pair can be used for only one ` + - `pending request at the same time. ` + - `Continue the loop execution to the next wallet...` - ) - continue - } - - const walletBTCBalance = mainUtxo.value.sub(pendingRedemptionsValue) - - // Save the max possible redemption amount. - maxAmount = walletBTCBalance.gt(maxAmount) ? walletBTCBalance : maxAmount - - if (walletBTCBalance.gte(amount)) { - walletData = { - walletPublicKey: walletPublicKey.toString(), - mainUtxo, - } - - break - } - - console.debug( - `The wallet (${walletPublicKeyHash.toString()})` + - `cannot handle the redemption request. ` + - `Continue the loop execution to the next wallet...` - ) - } - - if (liveWalletsCounter === 0) { - throw new Error("Currently, there are no live wallets in the network.") - } - - // Cover a corner case when the user requested redemption for all live wallets - // in the network using the same Bitcoin address. - if (!walletData && liveWalletsCounter > 0 && maxAmount.eq(0)) { - throw new Error( - "All live wallets in the network have the pending redemption for a given Bitcoin address. Please use another Bitcoin address." - ) - } - - if (!walletData) - throw new Error( - `Could not find a wallet with enough funds. Maximum redemption amount is ${maxAmount} Satoshi.` - ) - - return walletData -} diff --git a/typescript/src/services/redemptions/index.ts b/typescript/src/services/redemptions/index.ts new file mode 100644 index 000000000..112a3ad8c --- /dev/null +++ b/typescript/src/services/redemptions/index.ts @@ -0,0 +1 @@ +export * from "./redemptions-service" diff --git a/typescript/src/services/redemptions/redemptions-service.ts b/typescript/src/services/redemptions/redemptions-service.ts new file mode 100644 index 000000000..2b99e7fbd --- /dev/null +++ b/typescript/src/services/redemptions/redemptions-service.ts @@ -0,0 +1,363 @@ +import { + RedemptionRequest, + TBTCContracts, + WalletState, +} from "../../lib/contracts" +import { + BitcoinAddressConverter, + BitcoinClient, + BitcoinNetwork, + BitcoinTxOutput, + BitcoinUtxo, +} from "../../lib/bitcoin" +import { BigNumber } from "ethers" +import { Hex } from "../../lib/utils" + +/** + * Service exposing features related to tBTC v2 redemptions. + */ +export class RedemptionsService { + /** + * Handle to tBTC contracts. + */ + private readonly tbtcContracts: TBTCContracts + /** + * Bitcoin client handle. + */ + private readonly bitcoinClient: BitcoinClient + + constructor(tbtcContracts: TBTCContracts, bitcoinClient: BitcoinClient) { + this.tbtcContracts = tbtcContracts + this.bitcoinClient = bitcoinClient + } + + /** + * Requests a redemption of TBTC v2 token into BTC. + * @param bitcoinRedeemerAddress Bitcoin address redeemed BTC should be + * sent to. Only P2PKH, P2WPKH, P2SH, and P2WSH + * address types are supported. + * @param amount The amount to be redeemed with the precision of the tBTC + * on-chain token contract. + * @returns Object containing: + * - Target chain hash of the request redemption transaction + * (for example, Ethereum transaction hash) + * - Bitcoin public key of the wallet asked to handle the redemption. + * Presented in the compressed form (33 bytes long with 02 or 03 prefix). + */ + async requestRedemption( + bitcoinRedeemerAddress: string, + amount: BigNumber + ): Promise<{ + targetChainTxHash: Hex + walletPublicKey: string + }> { + const redeemerOutputScript = BitcoinAddressConverter.addressToOutputScript( + bitcoinRedeemerAddress + ).toString() + + // TODO: Validate the given script is supported for redemption. + + const { walletPublicKey, mainUtxo } = await this.findWalletForRedemption( + redeemerOutputScript, + amount + ) + + const txHash = await this.tbtcContracts.tbtcToken.requestRedemption( + walletPublicKey, + mainUtxo, + redeemerOutputScript, + amount + ) + + return { + targetChainTxHash: txHash, + walletPublicKey, + } + } + + /** + * Finds the oldest live wallet that has enough BTC to handle a redemption + * request. + * @param redeemerOutputScript The redeemer output script the redeemed funds are + * supposed to be locked on. Must be un-prefixed and not prepended with + * length. + * @param amount The amount to be redeemed in satoshis. + * @returns Promise with the wallet details needed to request a redemption. + */ + protected async findWalletForRedemption( + redeemerOutputScript: string, + amount: BigNumber + ): Promise<{ + walletPublicKey: string + mainUtxo: BitcoinUtxo + }> { + const wallets = + await this.tbtcContracts.bridge.getNewWalletRegisteredEvents() + + let walletData: + | { + walletPublicKey: string + mainUtxo: BitcoinUtxo + } + | undefined = undefined + let maxAmount = BigNumber.from(0) + let liveWalletsCounter = 0 + + const bitcoinNetwork = await this.bitcoinClient.getNetwork() + + for (const wallet of wallets) { + const { walletPublicKeyHash } = wallet + const { state, walletPublicKey, pendingRedemptionsValue } = + await this.tbtcContracts.bridge.wallets(walletPublicKeyHash) + + // Wallet must be in Live state. + if (state !== WalletState.Live) { + console.debug( + `Wallet is not in Live state ` + + `(wallet public key hash: ${walletPublicKeyHash.toString()}). ` + + `Continue the loop execution to the next wallet...` + ) + continue + } + liveWalletsCounter++ + + // Wallet must have a main UTXO that can be determined. + const mainUtxo = await this.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + if (!mainUtxo) { + console.debug( + `Could not find matching UTXO on chains ` + + `for wallet public key hash (${walletPublicKeyHash.toString()}). ` + + `Continue the loop execution to the next wallet...` + ) + continue + } + + const pendingRedemption = + await this.tbtcContracts.bridge.pendingRedemptions( + walletPublicKey.toString(), + redeemerOutputScript + ) + + if (pendingRedemption.requestedAt != 0) { + console.debug( + `There is a pending redemption request from this wallet to the ` + + `same Bitcoin address. Given wallet public key hash` + + `(${walletPublicKeyHash.toString()}) and redeemer output script ` + + `(${redeemerOutputScript}) pair can be used for only one ` + + `pending request at the same time. ` + + `Continue the loop execution to the next wallet...` + ) + continue + } + + const walletBTCBalance = mainUtxo.value.sub(pendingRedemptionsValue) + + // Save the max possible redemption amount. + maxAmount = walletBTCBalance.gt(maxAmount) ? walletBTCBalance : maxAmount + + if (walletBTCBalance.gte(amount)) { + walletData = { + walletPublicKey: walletPublicKey.toString(), + mainUtxo, + } + + break + } + + console.debug( + `The wallet (${walletPublicKeyHash.toString()})` + + `cannot handle the redemption request. ` + + `Continue the loop execution to the next wallet...` + ) + } + + if (liveWalletsCounter === 0) { + throw new Error("Currently, there are no live wallets in the network.") + } + + // Cover a corner case when the user requested redemption for all live wallets + // in the network using the same Bitcoin address. + if (!walletData && liveWalletsCounter > 0 && maxAmount.eq(0)) { + throw new Error( + "All live wallets in the network have the pending redemption for a given Bitcoin address. " + + "Please use another Bitcoin address." + ) + } + + if (!walletData) + throw new Error( + `Could not find a wallet with enough funds. Maximum redemption amount is ${maxAmount} Satoshi.` + ) + + return walletData + } + + /** + * Determines the plain-text wallet main UTXO currently registered in the + * Bridge on-chain contract. The returned main UTXO can be undefined if the + * wallet does not have a main UTXO registered in the Bridge at the moment. + * + * WARNING: THIS FUNCTION CANNOT DETERMINE THE MAIN UTXO IF IT COMES FROM A + * BITCOIN TRANSACTION THAT IS NOT ONE OF THE LATEST FIVE TRANSACTIONS + * TARGETING THE GIVEN WALLET PUBLIC KEY HASH. HOWEVER, SUCH A CASE IS + * VERY UNLIKELY. + * + * @param walletPublicKeyHash - Public key hash of the wallet. + * @param bitcoinNetwork - Bitcoin network. + * @returns Promise holding the wallet main UTXO or undefined value. + */ + protected async determineWalletMainUtxo( + walletPublicKeyHash: Hex, + bitcoinNetwork: BitcoinNetwork + ): Promise { + const { mainUtxoHash } = await this.tbtcContracts.bridge.wallets( + walletPublicKeyHash + ) + + // Valid case when the wallet doesn't have a main UTXO registered into + // the Bridge. + if ( + mainUtxoHash.equals( + Hex.from( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + ) + ) { + return undefined + } + + // Declare a helper function that will try to determine the main UTXO for + // the given wallet address type. + const determine = async ( + witnessAddress: boolean + ): Promise => { + // Build the wallet Bitcoin address based on its public key hash. + const walletAddress = BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + witnessAddress, + bitcoinNetwork + ) + + // Get the wallet transaction history. The wallet main UTXO registered in the + // Bridge almost always comes from the latest BTC transaction made by the wallet. + // However, there may be cases where the BTC transaction was made but their + // SPV proof is not yet submitted to the Bridge thus the registered main UTXO + // points to the second last BTC transaction. In theory, such a gap between + // the actual latest BTC transaction and the registered main UTXO in the + // Bridge may be even wider. The exact behavior is a wallet implementation + // detail and not a protocol invariant so, it may be subject of changes. + // To cover the worst possible cases, we always take the five latest + // transactions made by the wallet for consideration. + const walletTransactions = await this.bitcoinClient.getTransactionHistory( + walletAddress, + 5 + ) + + // Get the wallet script based on the wallet address. This is required + // to find transaction outputs that lock funds on the wallet. + const walletScript = + BitcoinAddressConverter.addressToOutputScript(walletAddress) + const isWalletOutput = (output: BitcoinTxOutput) => + walletScript.equals(output.scriptPubKey) + + // Start iterating from the latest transaction as the chance it matches + // the wallet main UTXO is the highest. + for (let i = walletTransactions.length - 1; i >= 0; i--) { + const walletTransaction = walletTransactions[i] + + // Find the output that locks the funds on the wallet. Only such an output + // can be a wallet main UTXO. + const outputIndex = walletTransaction.outputs.findIndex(isWalletOutput) + + // Should never happen as all transactions come from wallet history. Just + // in case check whether the wallet output was actually found. + if (outputIndex < 0) { + console.error( + `wallet output for transaction ${walletTransaction.transactionHash.toString()} not found` + ) + continue + } + + // Build a candidate UTXO instance based on the detected output. + const utxo: BitcoinUtxo = { + transactionHash: walletTransaction.transactionHash, + outputIndex: outputIndex, + value: walletTransaction.outputs[outputIndex].value, + } + + // Check whether the candidate UTXO hash matches the main UTXO hash stored + // on the Bridge. + if ( + mainUtxoHash.equals(this.tbtcContracts.bridge.buildUtxoHash(utxo)) + ) { + return utxo + } + } + + return undefined + } + + // The most common case is that the wallet uses a witness address for all + // operations. Try to determine the main UTXO for that address first as the + // chance for success is the highest here. + const mainUtxo = await determine(true) + + // In case the main UTXO was not found for witness address, there is still + // a chance it exists for the legacy wallet address. + return mainUtxo ?? (await determine(false)) + } + + /** + * Gets data of a registered redemption request from the Bridge contract. + * @param bitcoinRedeemerAddress Bitcoin redeemer address used to request + * the redemption. + * @param walletPublicKey Bitcoin public key of the wallet handling the + * redemption. Must be in the compressed form + * (33 bytes long with 02 or 03 prefix). + * @param type Type of redemption requests the function will look for. Can be + * either `pending` or `timedOut`. By default, `pending` is used. + * @returns Matching redemption requests. + * @throws Throws an error if no redemption request exists for the given + * input parameters. + */ + async getRedemptionRequests( + bitcoinRedeemerAddress: string, + walletPublicKey: string, + type: "pending" | "timedOut" = "pending" + ): Promise { + const redeemerOutputScript = BitcoinAddressConverter.addressToOutputScript( + bitcoinRedeemerAddress + ).toString() + + let redemptionRequest: RedemptionRequest | undefined = undefined + + switch (type) { + case "pending": { + redemptionRequest = await this.tbtcContracts.bridge.pendingRedemptions( + walletPublicKey, + redeemerOutputScript + ) + break + } + case "timedOut": { + redemptionRequest = await this.tbtcContracts.bridge.timedOutRedemptions( + walletPublicKey, + redeemerOutputScript + ) + break + } + default: { + throw new Error("Unsupported redemption request type") + } + } + + if (!redemptionRequest || redemptionRequest.requestedAt == 0) { + throw new Error("Redemption request does not exist") + } + + return redemptionRequest + } +} diff --git a/typescript/src/wallet.ts b/typescript/src/wallet.ts deleted file mode 100644 index 8b2f0a6d5..000000000 --- a/typescript/src/wallet.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Hex } from "./lib/utils" -import { Bridge } from "./lib/contracts" -import { - BitcoinClient, - BitcoinNetwork, - BitcoinAddressConverter, - BitcoinTxOutput, - BitcoinUtxo, -} from "./lib/bitcoin" - -/** - * Determines the plain-text wallet main UTXO currently registered in the - * Bridge on-chain contract. The returned main UTXO can be undefined if the - * wallet does not have a main UTXO registered in the Bridge at the moment. - * - * WARNING: THIS FUNCTION CANNOT DETERMINE THE MAIN UTXO IF IT COMES FROM A - * BITCOIN TRANSACTION THAT IS NOT ONE OF THE LATEST FIVE TRANSACTIONS - * TARGETING THE GIVEN WALLET PUBLIC KEY HASH. HOWEVER, SUCH A CASE IS - * VERY UNLIKELY. - * - * @param walletPublicKeyHash - Public key hash of the wallet. - * @param bridge - The handle to the Bridge on-chain contract. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @param bitcoinNetwork - Bitcoin network. - * @returns Promise holding the wallet main UTXO or undefined value. - */ -export async function determineWalletMainUtxo( - walletPublicKeyHash: Hex, - bridge: Bridge, - bitcoinClient: BitcoinClient, - bitcoinNetwork: BitcoinNetwork -): Promise { - const { mainUtxoHash } = await bridge.wallets(walletPublicKeyHash) - - // Valid case when the wallet doesn't have a main UTXO registered into - // the Bridge. - if ( - mainUtxoHash.equals( - Hex.from( - "0x0000000000000000000000000000000000000000000000000000000000000000" - ) - ) - ) { - return undefined - } - - // Declare a helper function that will try to determine the main UTXO for - // the given wallet address type. - const determine = async ( - witnessAddress: boolean - ): Promise => { - // Build the wallet Bitcoin address based on its public key hash. - const walletAddress = BitcoinAddressConverter.publicKeyHashToAddress( - walletPublicKeyHash.toString(), - witnessAddress, - bitcoinNetwork - ) - - // Get the wallet transaction history. The wallet main UTXO registered in the - // Bridge almost always comes from the latest BTC transaction made by the wallet. - // However, there may be cases where the BTC transaction was made but their - // SPV proof is not yet submitted to the Bridge thus the registered main UTXO - // points to the second last BTC transaction. In theory, such a gap between - // the actual latest BTC transaction and the registered main UTXO in the - // Bridge may be even wider. The exact behavior is a wallet implementation - // detail and not a protocol invariant so, it may be subject of changes. - // To cover the worst possible cases, we always take the five latest - // transactions made by the wallet for consideration. - const walletTransactions = await bitcoinClient.getTransactionHistory( - walletAddress, - 5 - ) - - // Get the wallet script based on the wallet address. This is required - // to find transaction outputs that lock funds on the wallet. - const walletScript = - BitcoinAddressConverter.addressToOutputScript(walletAddress) - const isWalletOutput = (output: BitcoinTxOutput) => - walletScript.equals(output.scriptPubKey) - - // Start iterating from the latest transaction as the chance it matches - // the wallet main UTXO is the highest. - for (let i = walletTransactions.length - 1; i >= 0; i--) { - const walletTransaction = walletTransactions[i] - - // Find the output that locks the funds on the wallet. Only such an output - // can be a wallet main UTXO. - const outputIndex = walletTransaction.outputs.findIndex(isWalletOutput) - - // Should never happen as all transactions come from wallet history. Just - // in case check whether the wallet output was actually found. - if (outputIndex < 0) { - console.error( - `wallet output for transaction ${walletTransaction.transactionHash.toString()} not found` - ) - continue - } - - // Build a candidate UTXO instance based on the detected output. - const utxo: BitcoinUtxo = { - transactionHash: walletTransaction.transactionHash, - outputIndex: outputIndex, - value: walletTransaction.outputs[outputIndex].value, - } - - // Check whether the candidate UTXO hash matches the main UTXO hash stored - // on the Bridge. - if (mainUtxoHash.equals(bridge.buildUtxoHash(utxo))) { - return utxo - } - } - - return undefined - } - - // The most common case is that the wallet uses a witness address for all - // operations. Try to determine the main UTXO for that address first as the - // chance for success is the highest here. - const mainUtxo = await determine(true) - - // In case the main UTXO was not found for witness address, there is still - // a chance it exists for the legacy wallet address. - return mainUtxo ?? (await determine(false)) -} diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index ef90d3cae..f9a3a5c68 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -1,76 +1,817 @@ import { + BitcoinAddressConverter, + BitcoinHashUtils, BitcoinNetwork, - BitcoinTx, BitcoinRawTx, + BitcoinTx, BitcoinTxHash, BitcoinUtxo, } from "../src/lib/bitcoin" import bcoin from "bcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { - walletPrivateKey, - walletPublicKey, - singleP2PKHRedemptionWithWitnessChange, - singleP2WPKHRedemptionWithWitnessChange, - singleP2SHRedemptionWithWitnessChange, - singleP2WSHRedemptionWithWitnessChange, - multipleRedemptionsWithWitnessChange, + findWalletForRedemptionData, multipleRedemptionsWithoutChange, - singleP2SHRedemptionWithNonWitnessChange, - redemptionProof, + multipleRedemptionsWithWitnessChange, p2pkhWalletAddress, p2wpkhWalletAddress, + redemptionProof, RedemptionTestData, - findWalletForRedemptionData, + singleP2PKHRedemptionWithWitnessChange, + singleP2SHRedemptionWithNonWitnessChange, + singleP2SHRedemptionWithWitnessChange, + singleP2WPKHRedemptionWithWitnessChange, + singleP2WSHRedemptionWithWitnessChange, + walletPrivateKey, + walletPublicKey, } from "./data/redemption" -import { RedemptionRequest, Wallet } from "../src/lib/contracts" +import { + NewWalletRegisteredEvent, + RedemptionRequest, + Wallet, + WalletState, +} from "../src/lib/contracts" import { assembleRedemptionTransaction, - findWalletForRedemption, - getRedemptionRequest, - requestRedemption, submitRedemptionProof, submitRedemptionTransaction, } from "../src/redemption" import { MockBridge } from "./utils/mock-bridge" import * as chai from "chai" -import chaiAsPromised from "chai-as-promised" import { expect } from "chai" -import { BigNumberish, BigNumber } from "ethers" -import { MockTBTCToken } from "./utils/mock-tbtc-token" +import chaiAsPromised from "chai-as-promised" +import { BigNumber, BigNumberish } from "ethers" +import { RedemptionsService } from "../src/services/redemptions" +import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" +import { Hex } from "../src" chai.use(chaiAsPromised) describe("Redemption", () => { - describe("requestRedemption", () => { - const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange - const mainUtxo = data.mainUtxo - const redeemerOutputScript = - data.pendingRedemptions[0].pendingRedemption.redeemerOutputScript - const amount = data.pendingRedemptions[0].pendingRedemption.requestedAmount - const token: MockTBTCToken = new MockTBTCToken() + describe("RedemptionsService", () => { + describe("requestRedemption", () => { + const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange + const { transactionHash, value } = data.mainUtxo + const mainUtxo: BitcoinUtxo = { + transactionHash, + outputIndex: 0, + value, + } + const redeemerOutputScript = + data.pendingRedemptions[0].pendingRedemption.redeemerOutputScript + const amount = + data.pendingRedemptions[0].pendingRedemption.requestedAmount - beforeEach(async () => { - bcoin.set("testnet") + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient - await requestRedemption( - walletPublicKey, - mainUtxo, - redeemerOutputScript, - amount, - token + beforeEach(async () => { + bcoin.set("testnet") + + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + + const walletPublicKeyHash = Hex.from( + BitcoinHashUtils.computeHash160(walletPublicKey) + ) + + // Prepare NewWalletRegisteredEvent history. Set only relevant fields. + tbtcContracts.bridge.newWalletRegisteredEvents = [ + { + walletPublicKeyHash: walletPublicKeyHash, + } as NewWalletRegisteredEvent, + ] + + // Prepare wallet data in the Bridge. Set only relevant fields. + tbtcContracts.bridge.setWallet(walletPublicKeyHash.toPrefixedString(), { + state: WalletState.Live, + walletPublicKey: Hex.from(walletPublicKey), + pendingRedemptionsValue: BigNumber.from(0), + mainUtxoHash: tbtcContracts.bridge.buildUtxoHash(mainUtxo), + } as Wallet) + + const walletAddress = BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + true, + BitcoinNetwork.Testnet + ) + + // Prepare wallet transaction history for main UTXO lookup. + // Set only relevant fields. + const transactionHistory = new Map() + transactionHistory.set(walletAddress, [ + { + transactionHash: mainUtxo.transactionHash, + outputs: [ + { + outputIndex: mainUtxo.outputIndex, + value: mainUtxo.value, + scriptPubKey: + BitcoinAddressConverter.addressToOutputScript(walletAddress), + }, + ], + } as BitcoinTx, + ]) + bitcoinClient.transactionHistory = transactionHistory + + const redemptionsService = new RedemptionsService( + tbtcContracts, + bitcoinClient + ) + + await redemptionsService.requestRedemption( + BitcoinAddressConverter.outputScriptToAddress( + Hex.from(redeemerOutputScript), + BitcoinNetwork.Testnet + ), + amount + ) + }) + + it("should submit redemption request with correct arguments", () => { + const tokenLog = tbtcContracts.tbtcToken.requestRedemptionLog + + expect(tokenLog.length).to.equal(1) + expect(tokenLog[0]).to.deep.equal({ + walletPublicKey, + mainUtxo, + redeemerOutputScript, + amount, + }) + }) + }) + + describe("getRedemptionRequest", () => { + context("when asked for a pending request", () => { + const { redemptionKey, pendingRedemption: redemptionRequest } = + multipleRedemptionsWithWitnessChange.pendingRedemptions[0] + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let redemptionsService: RedemptionsService + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + + const pendingRedemptions = new Map() + pendingRedemptions.set(redemptionKey, redemptionRequest) + + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + + redemptionsService = new RedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + it("should return the expected redemption request", async () => { + const actualRedemptionRequest = + await redemptionsService.getRedemptionRequests( + BitcoinAddressConverter.outputScriptToAddress( + Hex.from(redemptionRequest.redeemerOutputScript), + BitcoinNetwork.Testnet + ), + walletPublicKey, + "pending" + ) + + expect(actualRedemptionRequest).to.be.eql(redemptionRequest) + }) + }) + + context("when asked for a timed-out request", () => { + const { redemptionKey, pendingRedemption: redemptionRequest } = + multipleRedemptionsWithWitnessChange.pendingRedemptions[0] + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let redemptionsService: RedemptionsService + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + + const timedOutRedemptions = new Map() + timedOutRedemptions.set(redemptionKey, redemptionRequest) + + tbtcContracts.bridge.setTimedOutRedemptions(timedOutRedemptions) + + redemptionsService = new RedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + it("should return the expected redemption request", async () => { + const actualRedemptionRequest = + await redemptionsService.getRedemptionRequests( + BitcoinAddressConverter.outputScriptToAddress( + Hex.from(redemptionRequest.redeemerOutputScript), + BitcoinNetwork.Testnet + ), + walletPublicKey, + "timedOut" + ) + + expect(actualRedemptionRequest).to.be.eql(redemptionRequest) + }) + }) + }) + + describe("findWalletForRedemption", () => { + class TestRedemptionsService extends RedemptionsService { + public async findWalletForRedemption( + redeemerOutputScript: string, + amount: BigNumber + ): Promise<{ + walletPublicKey: string + mainUtxo: BitcoinUtxo + }> { + return super.findWalletForRedemption(redeemerOutputScript, amount) + } + } + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let redemptionsService: TestRedemptionsService + // script for testnet P2WSH address + // tb1qau95mxzh2249aa3y8exx76ltc2sq0e7kw8hj04936rdcmnynhswqqz02vv + const redeemerOutputScript = + "0x220020ef0b4d985752aa5ef6243e4c6f6bebc2a007e7d671ef27d4b1d0db8dcc93bc1c" + + context( + "when there are no wallets in the network that can handle redemption", + () => { + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + beforeEach(() => { + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() + tbtcContracts.bridge.newWalletRegisteredEvents = [] + redemptionsService = new TestRedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + it("should throw an error", async () => { + await expect( + redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + ).to.be.rejectedWith( + "Currently, there are no live wallets in the network." + ) + }) + } ) + + context("when there are registered wallets in the network", () => { + let result: Awaited | never> + const walletsOrder = [ + findWalletForRedemptionData.nonLiveWallet, + findWalletForRedemptionData.walletWithoutUtxo, + findWalletForRedemptionData.walletWithPendingRedemption, + findWalletForRedemptionData.liveWallet, + ] + + beforeEach(async () => { + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() + + tbtcContracts.bridge.newWalletRegisteredEvents = walletsOrder.map( + (wallet) => wallet.event + ) + + const walletsTransactionHistory = new Map() + + walletsOrder.forEach((wallet) => { + const { + state, + mainUtxoHash, + walletPublicKey, + btcAddress, + transactions, + pendingRedemptionsValue, + } = wallet.data + + walletsTransactionHistory.set(btcAddress, transactions) + tbtcContracts.bridge.setWallet( + wallet.event.walletPublicKeyHash.toPrefixedString(), + { + state, + mainUtxoHash, + walletPublicKey, + pendingRedemptionsValue, + } as Wallet + ) + }) + + bitcoinClient.transactionHistory = walletsTransactionHistory + + redemptionsService = new TestRedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + context( + "when there is a wallet that can handle the redemption request", + () => { + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + beforeEach(async () => { + result = await redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + }) + + it("should get all registered wallets", () => { + const bridgeQueryEventsLog = + tbtcContracts.bridge.newWalletRegisteredEventsLog + + expect(bridgeQueryEventsLog.length).to.equal(1) + expect(bridgeQueryEventsLog[0]).to.deep.equal({ + options: undefined, + filterArgs: [], + }) + }) + + it("should return the wallet data that can handle redemption request", () => { + const expectedWalletData = + findWalletForRedemptionData.walletWithPendingRedemption.data + + expect(result).to.deep.eq({ + walletPublicKey: expectedWalletData.walletPublicKey.toString(), + mainUtxo: expectedWalletData.mainUtxo, + }) + }) + } + ) + + context( + "when the redemption request amount is too large and no wallet can handle the request", + () => { + const amount = BigNumber.from("10000000000") // 1 000 BTC + const expectedMaxAmount = walletsOrder + .map((wallet) => wallet.data) + .map((wallet) => wallet.mainUtxo) + .map((utxo) => utxo.value) + .sort((a, b) => (b.gt(a) ? 0 : -1))[0] + + it("should throw an error", async () => { + await expect( + redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + ).to.be.rejectedWith( + `Could not find a wallet with enough funds. Maximum redemption amount is ${expectedMaxAmount.toString()} Satoshi.` + ) + }) + } + ) + + context( + "when there is pending redemption request from a given wallet to the same address", + () => { + beforeEach(async () => { + const redeemerOutputScript = + findWalletForRedemptionData.pendingRedemption + .redeemerOutputScript + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + + const walletPublicKeyHash = + findWalletForRedemptionData.walletWithPendingRedemption.event + .walletPublicKeyHash + + const pendingRedemptions = new Map< + BigNumberish, + RedemptionRequest + >() + + const key = MockBridge.buildRedemptionKey( + walletPublicKeyHash.toString(), + redeemerOutputScript + ) + + pendingRedemptions.set( + key, + findWalletForRedemptionData.pendingRedemption + ) + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + + result = await redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + }) + + it("should get all registered wallets", () => { + const bridgeQueryEventsLog = + tbtcContracts.bridge.newWalletRegisteredEventsLog + + expect(bridgeQueryEventsLog.length).to.equal(1) + expect(bridgeQueryEventsLog[0]).to.deep.equal({ + options: undefined, + filterArgs: [], + }) + }) + + it("should skip the wallet for which there is a pending redemption request to the same redeemer output script and return the wallet data that can handle redemption request", () => { + const expectedWalletData = + findWalletForRedemptionData.liveWallet.data + + expect(result).to.deep.eq({ + walletPublicKey: expectedWalletData.walletPublicKey.toString(), + mainUtxo: expectedWalletData.mainUtxo, + }) + }) + } + ) + + context( + "when wallet has pending redemptions and the requested amount is greater than possible", + () => { + beforeEach(async () => { + const wallet = + findWalletForRedemptionData.walletWithPendingRedemption + const walletBTCBalance = wallet.data.mainUtxo.value + + const amount: BigNumber = walletBTCBalance + .sub(wallet.data.pendingRedemptionsValue) + .add(BigNumber.from(500000)) // 0.005 BTC + + console.log("amount", amount.toString()) + + result = await redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + }) + + it("should skip the wallet wallet with pending redemptions and return the wallet data that can handle redemption request ", () => { + const expectedWalletData = + findWalletForRedemptionData.liveWallet.data + + expect(result).to.deep.eq({ + walletPublicKey: expectedWalletData.walletPublicKey.toString(), + mainUtxo: expectedWalletData.mainUtxo, + }) + }) + } + ) + + context( + "when all active wallets has pending redemption for a given Bitcoin address", + () => { + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + const redeemerOutputScript = + findWalletForRedemptionData.pendingRedemption.redeemerOutputScript + + beforeEach(async () => { + const walletPublicKeyHash = + findWalletForRedemptionData.walletWithPendingRedemption.event + .walletPublicKeyHash + + const pendingRedemptions = new Map< + BigNumberish, + RedemptionRequest + >() + + const pendingRedemption1 = MockBridge.buildRedemptionKey( + walletPublicKeyHash.toString(), + redeemerOutputScript + ) + + const pendingRedemption2 = MockBridge.buildRedemptionKey( + findWalletForRedemptionData.liveWallet.event.walletPublicKeyHash.toString(), + redeemerOutputScript + ) + + pendingRedemptions.set( + pendingRedemption1, + findWalletForRedemptionData.pendingRedemption + ) + + pendingRedemptions.set( + pendingRedemption2, + findWalletForRedemptionData.pendingRedemption + ) + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + }) + + it("should throw an error", async () => { + await expect( + redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + ).to.be.rejectedWith( + "All live wallets in the network have the pending redemption for a given Bitcoin address. Please use another Bitcoin address." + ) + }) + } + ) + }) }) - it("should submit redemption proof with correct arguments", () => { - const tokenLog = token.requestRedemptionLog + describe("determineWalletMainUtxo", () => { + class TestRedemptionsService extends RedemptionsService { + public async determineWalletMainUtxo( + walletPublicKeyHash: Hex, + bitcoinNetwork: BitcoinNetwork + ): Promise { + return super.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + } + } - expect(tokenLog.length).to.equal(1) - expect(tokenLog[0]).to.deep.equal({ - walletPublicKey, - mainUtxo, - redeemerOutputScript, - amount, + // Just an arbitrary 20-byte wallet public key hash. + const walletPublicKeyHash = Hex.from( + "e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0" + ) + + // Helper function facilitating creation of mock transactions. + const mockTransaction = ( + hash: string, + outputs: Record // key: locking script, value: amount of locked satoshis + ): BitcoinTx => { + return { + transactionHash: Hex.from(hash), + inputs: [], // not relevant in this test scenario + outputs: Object.entries(outputs).map( + ([scriptPubKey, value], index) => ({ + outputIndex: index, + value: BigNumber.from(value), + scriptPubKey: Hex.from(scriptPubKey), + }) + ), + } + } + + // Create a fake wallet witness transaction history that consists of 6 transactions. + const walletWitnessTransactionHistory: BitcoinTx[] = [ + mockTransaction( + "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", + { + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "4c6b33b7c0550e0e536a5d119ac7189d71e1296fcb0c258e0c115356895bc0e6", + { + "00140000000000000000000000000000000000000001": 100000, + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output + } + ), + mockTransaction( + "44863a79ce2b8fec9792403d5048506e50ffa7338191db0e6c30d3d3358ea2f6", + { + "00140000000000000000000000000000000000000001": 100000, + "00140000000000000000000000000000000000000002": 200000, + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 300000, // wallet witness output + } + ), + mockTransaction( + "f65bc5029251f0042aedb37f90dbb2bfb63a2e81694beef9cae5ec62e954c22e", + { + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "2724545276df61f43f1e92c4b9f1dd3c9109595c022dbd9dc003efbad8ded38b", + { + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214", + { + "00140000000000000000000000000000000000000001": 100000, + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output + } + ), + ] + + // Create a fake wallet legacy transaction history that consists of 6 transactions. + const walletLegacyTransactionHistory: BitcoinTx[] = [ + mockTransaction( + "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267", + { + "00140000000000000000000000000000000000000001": 100000, + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output + } + ), + mockTransaction( + "b11bfc481b95769b8488bd661d5f61a35f7c3c757160494d63f6e04e532dfcb9", + { + "00140000000000000000000000000000000000000001": 100000, + "00140000000000000000000000000000000000000002": 200000, + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 300000, // wallet legacy output + } + ), + mockTransaction( + "7e91580d989f8541489a37431381ff9babd596111232f1bc7a1a1ba503c27dee", + { + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "5404e339ba82e6e52fcc24cb40029bed8425baa4c7f869626ef9de956186f910", + { + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94", + { + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "00cc0cd13fc4de7a15cb41ab6d58f8b31c75b6b9b4194958c381441a67d09b08", + { + "00140000000000000000000000000000000000000001": 100000, + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output + } + ), + ] + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let bitcoinNetwork: BitcoinNetwork + let redemptionsService: TestRedemptionsService + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + redemptionsService = new TestRedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + context("when wallet main UTXO is not set in the Bridge", () => { + beforeEach(async () => { + tbtcContracts.bridge.setWallet( + walletPublicKeyHash.toPrefixedString(), + { + mainUtxoHash: Hex.from( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ), + } as Wallet + ) + }) + + it("should return undefined", async () => { + const mainUtxo = await redemptionsService.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + + expect(mainUtxo).to.be.undefined + }) + }) + + context("when wallet main UTXO is set in the Bridge", () => { + const tests = [ + { + testName: "recent witness transaction", + // Set the main UTXO hash based on the latest transaction from walletWitnessTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" + ), + outputIndex: 1, + value: BigNumber.from(200000), + }, + expectedMainUtxo: { + transactionHash: Hex.from( + "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" + ), + outputIndex: 1, + value: BigNumber.from(200000), + }, + }, + { + testName: "recent legacy transaction", + // Set the main UTXO hash based on the second last transaction from walletLegacyTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" + ), + outputIndex: 0, + value: BigNumber.from(100000), + }, + expectedMainUtxo: { + transactionHash: Hex.from( + "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" + ), + outputIndex: 0, + value: BigNumber.from(100000), + }, + }, + { + testName: "old witness transaction", + // Set the main UTXO hash based on the oldest transaction from walletWitnessTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1" + ), + outputIndex: 0, + value: BigNumber.from(100000), + }, + expectedMainUtxo: undefined, + }, + { + testName: "old legacy transaction", + // Set the main UTXO hash based on the oldest transaction from walletLegacyTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267" + ), + outputIndex: 1, + value: BigNumber.from(200000), + }, + expectedMainUtxo: undefined, + }, + ] + + tests.forEach(({ testName, actualMainUtxo, expectedMainUtxo }) => { + context(`with main UTXO coming from ${testName}`, () => { + const networkTests = [ + { + networkTestName: "bitcoin testnet", + network: BitcoinNetwork.Testnet, + }, + { + networkTestName: "bitcoin mainnet", + network: BitcoinNetwork.Mainnet, + }, + ] + + networkTests.forEach(({ networkTestName, network }) => { + context(`with ${networkTestName} network`, () => { + beforeEach(async () => { + bitcoinNetwork = network + + const walletWitnessAddress = + BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + true, + bitcoinNetwork + ) + const walletLegacyAddress = + BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + false, + bitcoinNetwork + ) + + // Record the fake transaction history for both address types. + const transactionHistory = new Map() + transactionHistory.set( + walletWitnessAddress, + walletWitnessTransactionHistory + ) + transactionHistory.set( + walletLegacyAddress, + walletLegacyTransactionHistory + ) + bitcoinClient.transactionHistory = transactionHistory + + tbtcContracts.bridge.setWallet( + walletPublicKeyHash.toPrefixedString(), + { + mainUtxoHash: + tbtcContracts.bridge.buildUtxoHash(actualMainUtxo), + } as Wallet + ) + }) + + it("should return the expected main UTXO", async () => { + const mainUtxo = + await redemptionsService.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + + expect(mainUtxo).to.be.eql(expectedMainUtxo) + }) + }) + }) + }) + }) }) }) }) @@ -437,9 +1178,7 @@ describe("Redemption", () => { redeemerOutputScripts, data.witness ) - ).to.be.rejectedWith( - "Provided redeemer output script and wallet public key do not identify a redemption request" - ) + ).to.be.rejectedWith("Redemption request does not exist") }) } ) @@ -1383,351 +2122,6 @@ describe("Redemption", () => { ) }) }) - - describe("getRedemptionRequest", () => { - context("when asked for a pending request", () => { - const { redemptionKey, pendingRedemption: redemptionRequest } = - multipleRedemptionsWithWitnessChange.pendingRedemptions[0] - - let bridge: MockBridge - - beforeEach(async () => { - const pendingRedemptions = new Map() - pendingRedemptions.set(redemptionKey, redemptionRequest) - - bridge = new MockBridge() - bridge.setPendingRedemptions(pendingRedemptions) - }) - - it("should return the expected redemption request", async () => { - const actualRedemptionRequest = await getRedemptionRequest( - walletPublicKey, - redemptionRequest.redeemerOutputScript, - "pending", - bridge - ) - - expect(actualRedemptionRequest).to.be.eql(redemptionRequest) - }) - }) - - context("when asked for a timed-out request", () => { - const { redemptionKey, pendingRedemption: redemptionRequest } = - multipleRedemptionsWithWitnessChange.pendingRedemptions[0] - - let bridge: MockBridge - - beforeEach(async () => { - const pendingRedemptions = new Map() - pendingRedemptions.set(redemptionKey, redemptionRequest) - - bridge = new MockBridge() - bridge.setTimedOutRedemptions(pendingRedemptions) - }) - - it("should return the expected redemption request", async () => { - const actualRedemptionRequest = await getRedemptionRequest( - walletPublicKey, - redemptionRequest.redeemerOutputScript, - "timedOut", - bridge - ) - - expect(actualRedemptionRequest).to.be.eql(redemptionRequest) - }) - }) - }) - - describe("findWalletForRedemption", () => { - let bridge: MockBridge - let bitcoinClient: MockBitcoinClient - // script for testnet P2WSH address - // tb1qau95mxzh2249aa3y8exx76ltc2sq0e7kw8hj04936rdcmnynhswqqz02vv - const redeemerOutputScript = - "0x220020ef0b4d985752aa5ef6243e4c6f6bebc2a007e7d671ef27d4b1d0db8dcc93bc1c" - - context( - "when there are no wallets in the network that can handle redemption", - () => { - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - beforeEach(() => { - bitcoinClient = new MockBitcoinClient() - bridge = new MockBridge() - bridge.newWalletRegisteredEvents = [] - }) - - it("should throw an error", async () => { - await expect( - findWalletForRedemption( - amount, - redeemerOutputScript, - BitcoinNetwork.Testnet, - bridge, - bitcoinClient - ) - ).to.be.rejectedWith( - "Currently, there are no live wallets in the network." - ) - }) - } - ) - - context("when there are registered wallets in the network", () => { - let result: Awaited | never> - const walletsOrder = [ - findWalletForRedemptionData.nonLiveWallet, - findWalletForRedemptionData.walletWithoutUtxo, - findWalletForRedemptionData.walletWithPendingRedemption, - findWalletForRedemptionData.liveWallet, - ] - - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - bridge = new MockBridge() - - bridge.newWalletRegisteredEvents = walletsOrder.map( - (wallet) => wallet.event - ) - - const walletsTransactionHistory = new Map() - - walletsOrder.forEach((wallet) => { - const { - state, - mainUtxoHash, - walletPublicKey, - btcAddress, - transactions, - pendingRedemptionsValue, - } = wallet.data - - walletsTransactionHistory.set(btcAddress, transactions) - bridge.setWallet( - wallet.event.walletPublicKeyHash.toPrefixedString(), - { - state, - mainUtxoHash, - walletPublicKey, - pendingRedemptionsValue, - } as Wallet - ) - }) - - bitcoinClient.transactionHistory = walletsTransactionHistory - }) - - context( - "when there is a wallet that can handle the redemption request", - () => { - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - beforeEach(async () => { - result = await findWalletForRedemption( - amount, - redeemerOutputScript, - BitcoinNetwork.Testnet, - bridge, - bitcoinClient - ) - }) - - it("should get all registered wallets", () => { - const bridgeQueryEventsLog = bridge.newWalletRegisteredEventsLog - - expect(bridgeQueryEventsLog.length).to.equal(1) - expect(bridgeQueryEventsLog[0]).to.deep.equal({ - options: undefined, - filterArgs: [], - }) - }) - - it("should return the wallet data that can handle redemption request", () => { - const expectedWalletData = - findWalletForRedemptionData.walletWithPendingRedemption.data - - expect(result).to.deep.eq({ - walletPublicKey: expectedWalletData.walletPublicKey.toString(), - mainUtxo: expectedWalletData.mainUtxo, - }) - }) - } - ) - - context( - "when the redemption request amount is too large and no wallet can handle the request", - () => { - const amount = BigNumber.from("10000000000") // 1 000 BTC - const expectedMaxAmount = walletsOrder - .map((wallet) => wallet.data) - .map((wallet) => wallet.mainUtxo) - .map((utxo) => utxo.value) - .sort((a, b) => (b.gt(a) ? 0 : -1))[0] - - it("should throw an error", async () => { - await expect( - findWalletForRedemption( - amount, - redeemerOutputScript, - BitcoinNetwork.Testnet, - bridge, - bitcoinClient - ) - ).to.be.rejectedWith( - `Could not find a wallet with enough funds. Maximum redemption amount is ${expectedMaxAmount.toString()} Satoshi.` - ) - }) - } - ) - - context( - "when there is pending redemption request from a given wallet to the same address", - () => { - beforeEach(async () => { - const redeemerOutputScript = - findWalletForRedemptionData.pendingRedemption.redeemerOutputScript - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - - const walletPublicKeyHash = - findWalletForRedemptionData.walletWithPendingRedemption.event - .walletPublicKeyHash - - const pendingRedemptions = new Map< - BigNumberish, - RedemptionRequest - >() - - const key = MockBridge.buildRedemptionKey( - walletPublicKeyHash.toString(), - redeemerOutputScript - ) - - pendingRedemptions.set( - key, - findWalletForRedemptionData.pendingRedemption - ) - bridge.setPendingRedemptions(pendingRedemptions) - - result = await findWalletForRedemption( - amount, - redeemerOutputScript, - BitcoinNetwork.Testnet, - bridge, - bitcoinClient - ) - }) - - it("should get all registered wallets", () => { - const bridgeQueryEventsLog = bridge.newWalletRegisteredEventsLog - - expect(bridgeQueryEventsLog.length).to.equal(1) - expect(bridgeQueryEventsLog[0]).to.deep.equal({ - options: undefined, - filterArgs: [], - }) - }) - - it("should skip the wallet for which there is a pending redemption request to the same redeemer output script and return the wallet data that can handle redemption request", () => { - const expectedWalletData = - findWalletForRedemptionData.liveWallet.data - - expect(result).to.deep.eq({ - walletPublicKey: expectedWalletData.walletPublicKey.toString(), - mainUtxo: expectedWalletData.mainUtxo, - }) - }) - } - ) - - context( - "when wallet has pending redemptions and the requested amount is greater than possible", - () => { - beforeEach(async () => { - const wallet = - findWalletForRedemptionData.walletWithPendingRedemption - const walletBTCBalance = wallet.data.mainUtxo.value - - const amount: BigNumber = walletBTCBalance - .sub(wallet.data.pendingRedemptionsValue) - .add(BigNumber.from(500000)) // 0.005 BTC - - console.log("amount", amount.toString()) - - result = await findWalletForRedemption( - amount, - redeemerOutputScript, - BitcoinNetwork.Testnet, - bridge, - bitcoinClient - ) - }) - - it("should skip the wallet wallet with pending redemptions and return the wallet data that can handle redemption request ", () => { - const expectedWalletData = - findWalletForRedemptionData.liveWallet.data - - expect(result).to.deep.eq({ - walletPublicKey: expectedWalletData.walletPublicKey.toString(), - mainUtxo: expectedWalletData.mainUtxo, - }) - }) - } - ) - - context( - "when all active wallets has pending redemption for a given Bitcoin address", - () => { - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - const redeemerOutputScript = - findWalletForRedemptionData.pendingRedemption.redeemerOutputScript - - beforeEach(async () => { - const walletPublicKeyHash = - findWalletForRedemptionData.walletWithPendingRedemption.event - .walletPublicKeyHash - - const pendingRedemptions = new Map< - BigNumberish, - RedemptionRequest - >() - - const pendingRedemption1 = MockBridge.buildRedemptionKey( - walletPublicKeyHash.toString(), - redeemerOutputScript - ) - - const pendingRedemption2 = MockBridge.buildRedemptionKey( - findWalletForRedemptionData.liveWallet.event.walletPublicKeyHash.toString(), - redeemerOutputScript - ) - - pendingRedemptions.set( - pendingRedemption1, - findWalletForRedemptionData.pendingRedemption - ) - - pendingRedemptions.set( - pendingRedemption2, - findWalletForRedemptionData.pendingRedemption - ) - bridge.setPendingRedemptions(pendingRedemptions) - }) - - it("should throw an error", async () => { - await expect( - findWalletForRedemption( - amount, - redeemerOutputScript, - BitcoinNetwork.Testnet, - bridge, - bitcoinClient - ) - ).to.be.rejectedWith( - "All live wallets in the network have the pending redemption for a given Bitcoin address. Please use another Bitcoin address." - ) - }) - } - ) - }) - }) }) async function runRedemptionScenario( diff --git a/typescript/test/wallet.test.ts b/typescript/test/wallet.test.ts deleted file mode 100644 index 5cbd48fce..000000000 --- a/typescript/test/wallet.test.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import { MockBridge } from "./utils/mock-bridge" -import { Hex } from "../src" -import { determineWalletMainUtxo } from "../src/wallet" -import { expect } from "chai" -import { - BitcoinNetwork, - BitcoinAddressConverter, - BitcoinTx, -} from "../src/lib/bitcoin" -import { Wallet } from "../src/lib/contracts" -import { BigNumber } from "ethers" - -describe("Wallet", () => { - describe("determineWalletMainUtxo", () => { - // Just an arbitrary 20-byte wallet public key hash. - const walletPublicKeyHash = Hex.from( - "e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0" - ) - - // Helper function facilitating creation of mock transactions. - const mockTransaction = ( - hash: string, - outputs: Record // key: locking script, value: amount of locked satoshis - ): BitcoinTx => { - return { - transactionHash: Hex.from(hash), - inputs: [], // not relevant in this test scenario - outputs: Object.entries(outputs).map( - ([scriptPubKey, value], index) => ({ - outputIndex: index, - value: BigNumber.from(value), - scriptPubKey: Hex.from(scriptPubKey), - }) - ), - } - } - - // Create a fake wallet witness transaction history that consists of 6 transactions. - const walletWitnessTransactionHistory: BitcoinTx[] = [ - mockTransaction( - "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", - { - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "4c6b33b7c0550e0e536a5d119ac7189d71e1296fcb0c258e0c115356895bc0e6", - { - "00140000000000000000000000000000000000000001": 100000, - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output - } - ), - mockTransaction( - "44863a79ce2b8fec9792403d5048506e50ffa7338191db0e6c30d3d3358ea2f6", - { - "00140000000000000000000000000000000000000001": 100000, - "00140000000000000000000000000000000000000002": 200000, - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 300000, // wallet witness output - } - ), - mockTransaction( - "f65bc5029251f0042aedb37f90dbb2bfb63a2e81694beef9cae5ec62e954c22e", - { - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "2724545276df61f43f1e92c4b9f1dd3c9109595c022dbd9dc003efbad8ded38b", - { - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214", - { - "00140000000000000000000000000000000000000001": 100000, - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output - } - ), - ] - - // Create a fake wallet legacy transaction history that consists of 6 transactions. - const walletLegacyTransactionHistory: BitcoinTx[] = [ - mockTransaction( - "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267", - { - "00140000000000000000000000000000000000000001": 100000, - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output - } - ), - mockTransaction( - "b11bfc481b95769b8488bd661d5f61a35f7c3c757160494d63f6e04e532dfcb9", - { - "00140000000000000000000000000000000000000001": 100000, - "00140000000000000000000000000000000000000002": 200000, - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 300000, // wallet legacy output - } - ), - mockTransaction( - "7e91580d989f8541489a37431381ff9babd596111232f1bc7a1a1ba503c27dee", - { - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "5404e339ba82e6e52fcc24cb40029bed8425baa4c7f869626ef9de956186f910", - { - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94", - { - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "00cc0cd13fc4de7a15cb41ab6d58f8b31c75b6b9b4194958c381441a67d09b08", - { - "00140000000000000000000000000000000000000001": 100000, - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output - } - ), - ] - - let bridge: MockBridge - let bitcoinClient: MockBitcoinClient - let bitcoinNetwork: BitcoinNetwork - - beforeEach(async () => { - bridge = new MockBridge() - bitcoinClient = new MockBitcoinClient() - }) - - context("when wallet main UTXO is not set in the Bridge", () => { - beforeEach(async () => { - bridge.setWallet(walletPublicKeyHash.toPrefixedString(), { - mainUtxoHash: Hex.from( - "0x0000000000000000000000000000000000000000000000000000000000000000" - ), - } as Wallet) - }) - - it("should return undefined", async () => { - const mainUtxo = await determineWalletMainUtxo( - walletPublicKeyHash, - bridge, - bitcoinClient, - bitcoinNetwork - ) - - expect(mainUtxo).to.be.undefined - }) - }) - - context("when wallet main UTXO is set in the Bridge", () => { - const tests = [ - { - testName: "recent witness transaction", - // Set the main UTXO hash based on the latest transaction from walletWitnessTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" - ), - outputIndex: 1, - value: BigNumber.from(200000), - }, - expectedMainUtxo: { - transactionHash: Hex.from( - "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" - ), - outputIndex: 1, - value: BigNumber.from(200000), - }, - }, - { - testName: "recent legacy transaction", - // Set the main UTXO hash based on the second last transaction from walletLegacyTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" - ), - outputIndex: 0, - value: BigNumber.from(100000), - }, - expectedMainUtxo: { - transactionHash: Hex.from( - "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" - ), - outputIndex: 0, - value: BigNumber.from(100000), - }, - }, - { - testName: "old witness transaction", - // Set the main UTXO hash based on the oldest transaction from walletWitnessTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1" - ), - outputIndex: 0, - value: BigNumber.from(100000), - }, - expectedMainUtxo: undefined, - }, - { - testName: "old legacy transaction", - // Set the main UTXO hash based on the oldest transaction from walletLegacyTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267" - ), - outputIndex: 1, - value: BigNumber.from(200000), - }, - expectedMainUtxo: undefined, - }, - ] - - tests.forEach(({ testName, actualMainUtxo, expectedMainUtxo }) => { - context(`with main UTXO coming from ${testName}`, () => { - const networkTests = [ - { - networkTestName: "bitcoin testnet", - network: BitcoinNetwork.Testnet, - }, - { - networkTestName: "bitcoin mainnet", - network: BitcoinNetwork.Mainnet, - }, - ] - - networkTests.forEach(({ networkTestName, network }) => { - context(`with ${networkTestName} network`, () => { - beforeEach(async () => { - bitcoinNetwork = network - - const walletWitnessAddress = - BitcoinAddressConverter.publicKeyHashToAddress( - walletPublicKeyHash.toString(), - true, - bitcoinNetwork - ) - const walletLegacyAddress = - BitcoinAddressConverter.publicKeyHashToAddress( - walletPublicKeyHash.toString(), - false, - bitcoinNetwork - ) - - // Record the fake transaction history for both address types. - const transactionHistory = new Map() - transactionHistory.set( - walletWitnessAddress, - walletWitnessTransactionHistory - ) - transactionHistory.set( - walletLegacyAddress, - walletLegacyTransactionHistory - ) - bitcoinClient.transactionHistory = transactionHistory - - bridge.setWallet(walletPublicKeyHash.toPrefixedString(), { - mainUtxoHash: bridge.buildUtxoHash(actualMainUtxo), - } as Wallet) - }) - - it("should return the expected main UTXO", async () => { - const mainUtxo = await determineWalletMainUtxo( - walletPublicKeyHash, - bridge, - bitcoinClient, - bitcoinNetwork - ) - - expect(mainUtxo).to.be.eql(expectedMainUtxo) - }) - }) - }) - }) - }) - }) - }) -}) From f0f7c1505262563779b915854232712662d8d0a7 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 29 Sep 2023 14:11:34 +0200 Subject: [PATCH 20/43] Move remaining code to the `./services/maintenance` feature module The code left is purely related to system maintainers or is helper tooling empowering internal operations like system testing. We are moving it to a dedicated module. By the way, we are arranging the main `index.ts` and adjust all imports to use the base path and not dive into specific directories. --- typescript/src/deposit-sweep.ts | 417 ---- typescript/src/index.ts | 64 +- typescript/src/optimistic-minting.ts | 76 - typescript/src/redemption.ts | 256 -- typescript/src/services/deposits/index.ts | 1 + typescript/src/services/maintenance/index.ts | 4 + .../maintenance/maintenance-service.ts | 25 + .../maintenance/optimistic-minting.ts | 80 + typescript/src/services/maintenance/spv.ts | 96 + .../src/services/maintenance/wallet-tx.ts | 663 ++++++ typescript/test/bitcoin-network.test.ts | 6 +- typescript/test/bitcoin.test.ts | 4 +- typescript/test/data/deposit-refund.ts | 6 +- typescript/test/data/deposit-sweep.ts | 8 +- typescript/test/data/deposit.ts | 2 +- typescript/test/data/electrum.ts | 4 +- typescript/test/data/proof.ts | 4 +- typescript/test/data/redemption.ts | 9 +- typescript/test/deposit-sweep.test.ts | 1713 +++++++------- typescript/test/deposit.test.ts | 14 +- typescript/test/electrum.test.ts | 4 +- typescript/test/ethereum.test.ts | 7 +- typescript/test/hex.test.ts | 2 +- typescript/test/proof.test.ts | 4 +- typescript/test/redemption.test.ts | 2068 +++++++++-------- 25 files changed, 2904 insertions(+), 2633 deletions(-) delete mode 100644 typescript/src/deposit-sweep.ts delete mode 100644 typescript/src/optimistic-minting.ts delete mode 100644 typescript/src/redemption.ts create mode 100644 typescript/src/services/maintenance/index.ts create mode 100644 typescript/src/services/maintenance/maintenance-service.ts create mode 100644 typescript/src/services/maintenance/optimistic-minting.ts create mode 100644 typescript/src/services/maintenance/spv.ts create mode 100644 typescript/src/services/maintenance/wallet-tx.ts diff --git a/typescript/src/deposit-sweep.ts b/typescript/src/deposit-sweep.ts deleted file mode 100644 index 440bfa219..000000000 --- a/typescript/src/deposit-sweep.ts +++ /dev/null @@ -1,417 +0,0 @@ -import bcoin from "bcoin" -import { BigNumber } from "ethers" -import { - assembleBitcoinSpvProof, - BitcoinRawTx, - BitcoinUtxo, - BitcoinClient, - extractBitcoinRawTxVectors, - BitcoinPublicKeyUtils, - BitcoinPrivateKeyUtils, - BitcoinTxHash, - BitcoinHashUtils, -} from "./lib/bitcoin" -import { Bridge, ChainIdentifier, DepositReceipt } from "./lib/contracts" -import { DepositScript } from "./services/deposits" - -/** - * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and - * broadcasting a Bitcoin P2(W)PKH deposit sweep transaction. - * @dev The caller is responsible for ensuring the provided UTXOs are correctly - * formed, can be spent by the wallet and their combined value is greater - * then the fee. Note that broadcasting transaction may fail silently (e.g. - * when the provided UTXOs are not spendable) and no error will be returned. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @param fee - the value that should be subtracted from the sum of the UTXOs - * values and used as the transaction fee. - * @param walletPrivateKey - Bitcoin private key of the wallet in WIF format. - * @param witness - The parameter used to decide about the type of the new main - * UTXO output. P2WPKH if `true`, P2PKH if `false`. - * @param utxos - P2(W)SH UTXOs to be combined into one output. - * @param deposits - Array of deposits. Each element corresponds to UTXO. - * The number of UTXOs and deposit elements must equal. - * @param mainUtxo - main UTXO of the wallet, which is a P2WKH UTXO resulting - * from the previous wallet transaction (optional). - * @returns The outcome consisting of: - * - the sweep transaction hash, - * - the new wallet's main UTXO produced by this transaction. - */ -export async function submitDepositSweepTransaction( - bitcoinClient: BitcoinClient, - fee: BigNumber, - walletPrivateKey: string, - witness: boolean, - utxos: BitcoinUtxo[], - deposits: DepositReceipt[], - mainUtxo?: BitcoinUtxo -): Promise<{ - transactionHash: BitcoinTxHash - newMainUtxo: BitcoinUtxo -}> { - const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] - for (const utxo of utxos) { - const utxoRawTransaction = await bitcoinClient.getRawTransaction( - utxo.transactionHash - ) - - utxosWithRaw.push({ - ...utxo, - transactionHex: utxoRawTransaction.transactionHex, - }) - } - - let mainUtxoWithRaw - - if (mainUtxo) { - const mainUtxoRawTransaction = await bitcoinClient.getRawTransaction( - mainUtxo.transactionHash - ) - mainUtxoWithRaw = { - ...mainUtxo, - transactionHex: mainUtxoRawTransaction.transactionHex, - } - } - - const { transactionHash, newMainUtxo, rawTransaction } = - await assembleDepositSweepTransaction( - fee, - walletPrivateKey, - witness, - utxosWithRaw, - deposits, - mainUtxoWithRaw - ) - - // Note that `broadcast` may fail silently (i.e. no error will be returned, - // even if the transaction is rejected by other nodes and does not enter the - // mempool, for example due to an UTXO being already spent). - await bitcoinClient.broadcast(rawTransaction) - - return { transactionHash, newMainUtxo } -} - -/** - * Assembles a Bitcoin P2WPKH deposit sweep transaction. - * @dev The caller is responsible for ensuring the provided UTXOs are correctly - * formed, can be spent by the wallet and their combined value is greater - * then the fee. - * @param fee - the value that should be subtracted from the sum of the UTXOs - * values and used as the transaction fee. - * @param walletPrivateKey - Bitcoin private key of the wallet in WIF format. - * @param witness - The parameter used to decide about the type of the new main - * UTXO output. P2WPKH if `true`, P2PKH if `false`. - * @param utxos - UTXOs from new deposit transactions. Must be P2(W)SH. - * @param deposits - Array of deposits. Each element corresponds to UTXO. - * The number of UTXOs and deposit elements must equal. - * @param mainUtxo - main UTXO of the wallet, which is a P2WKH UTXO resulting - * from the previous wallet transaction (optional). - * @returns The outcome consisting of: - * - the sweep transaction hash, - * - the new wallet's main UTXO produced by this transaction. - * - the sweep transaction in the raw format - */ -export async function assembleDepositSweepTransaction( - fee: BigNumber, - walletPrivateKey: string, - witness: boolean, - utxos: (BitcoinUtxo & BitcoinRawTx)[], - deposits: DepositReceipt[], - mainUtxo?: BitcoinUtxo & BitcoinRawTx -): Promise<{ - transactionHash: BitcoinTxHash - newMainUtxo: BitcoinUtxo - rawTransaction: BitcoinRawTx -}> { - if (utxos.length < 1) { - throw new Error("There must be at least one deposit UTXO to sweep") - } - - if (utxos.length != deposits.length) { - throw new Error("Number of UTXOs must equal the number of deposit elements") - } - - const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( - walletPrivateKey, - witness - ) - const walletAddress = walletKeyRing.getAddress("string") - - const inputCoins = [] - let totalInputValue = BigNumber.from(0) - - if (mainUtxo) { - inputCoins.push( - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), - mainUtxo.outputIndex, - -1 - ) - ) - totalInputValue = totalInputValue.add(mainUtxo.value) - } - - for (const utxo of utxos) { - inputCoins.push( - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), - utxo.outputIndex, - -1 - ) - ) - totalInputValue = totalInputValue.add(utxo.value) - } - - const transaction = new bcoin.MTX() - - transaction.addOutput({ - script: bcoin.Script.fromAddress(walletAddress), - value: totalInputValue.toNumber(), - }) - - await transaction.fund(inputCoins, { - changeAddress: walletAddress, - hardFee: fee.toNumber(), - subtractFee: true, - }) - - if (transaction.outputs.length != 1) { - throw new Error("Deposit sweep transaction must have only one output") - } - - // UTXOs must be mapped to deposits, as `fund` may arrange inputs in any - // order - const utxosWithDeposits: (BitcoinUtxo & BitcoinRawTx & DepositReceipt)[] = - utxos.map((utxo, index) => ({ - ...utxo, - ...deposits[index], - })) - - for (let i = 0; i < transaction.inputs.length; i++) { - const previousOutpoint = transaction.inputs[i].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - const previousScript = previousOutput.script - - // P2(W)PKH (main UTXO) - if (previousScript.isPubkeyhash() || previousScript.isWitnessPubkeyhash()) { - await signMainUtxoInput(transaction, i, walletKeyRing) - continue - } - - const utxoWithDeposit = utxosWithDeposits.find( - (u) => - u.transactionHash.toString() === previousOutpoint.txid() && - u.outputIndex == previousOutpoint.index - ) - if (!utxoWithDeposit) { - throw new Error("Unknown input") - } - - if (previousScript.isScripthash()) { - // P2SH (deposit UTXO) - await signP2SHDepositInput(transaction, i, utxoWithDeposit, walletKeyRing) - } else if (previousScript.isWitnessScripthash()) { - // P2WSH (deposit UTXO) - await signP2WSHDepositInput( - transaction, - i, - utxoWithDeposit, - walletKeyRing - ) - } else { - throw new Error("Unsupported UTXO script type") - } - } - - const transactionHash = BitcoinTxHash.from(transaction.txid()) - - return { - transactionHash, - newMainUtxo: { - transactionHash, - outputIndex: 0, // There is only one output. - value: BigNumber.from(transaction.outputs[0].value), - }, - rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), - }, - } -} - -/** - * Creates script for the transaction input at the given index and signs the - * input. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Empty promise. - */ -async function signMainUtxoInput( - transaction: any, - inputIndex: number, - walletKeyRing: any -) { - const previousOutpoint = transaction.inputs[inputIndex].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - if (!walletKeyRing.ownOutput(previousOutput)) { - throw new Error("UTXO does not belong to the wallet") - } - // Build script and set it as input's witness - transaction.scriptInput(inputIndex, previousOutput, walletKeyRing) - // Build signature and add it in front of script in input's witness - transaction.signInput(inputIndex, previousOutput, walletKeyRing) -} - -/** - * Creates and sets `scriptSig` for the transaction input at the given index by - * combining signature, wallet public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param deposit - Data of the deposit. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Empty promise. - */ -async function signP2SHDepositInput( - transaction: any, - inputIndex: number, - deposit: DepositReceipt, - walletKeyRing: any -): Promise { - const { walletPublicKey, depositScript, previousOutputValue } = - await prepareInputSignData(transaction, inputIndex, deposit, walletKeyRing) - - const signature: Buffer = transaction.signature( - inputIndex, - depositScript, - previousOutputValue, - walletKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 0 // legacy sighash version - ) - const scriptSig = new bcoin.Script() - scriptSig.clear() - scriptSig.pushData(signature) - scriptSig.pushData(Buffer.from(walletPublicKey, "hex")) - scriptSig.pushData(depositScript.toRaw()) - scriptSig.compile() - - transaction.inputs[inputIndex].script = scriptSig -} - -/** - * Creates and sets witness script for the transaction input at the given index - * by combining signature, wallet public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param deposit - Data of the deposit. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Empty promise. - */ -async function signP2WSHDepositInput( - transaction: any, - inputIndex: number, - deposit: DepositReceipt, - walletKeyRing: any -): Promise { - const { walletPublicKey, depositScript, previousOutputValue } = - await prepareInputSignData(transaction, inputIndex, deposit, walletKeyRing) - - const signature: Buffer = transaction.signature( - inputIndex, - depositScript, - previousOutputValue, - walletKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 1 // segwit sighash version - ) - - const witness = new bcoin.Witness() - witness.clear() - witness.pushData(signature) - witness.pushData(Buffer.from(walletPublicKey, "hex")) - witness.pushData(depositScript.toRaw()) - witness.compile() - - transaction.inputs[inputIndex].witness = witness -} - -/** - * Creates data needed to sign a deposit input. - * @param transaction - Mutable transaction containing the input. - * @param inputIndex - Index that points to the input. - * @param deposit - Data of the deposit. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Data needed to sign the input. - */ -async function prepareInputSignData( - transaction: any, - inputIndex: number, - deposit: DepositReceipt, - walletKeyRing: any -): Promise<{ - walletPublicKey: string - depositScript: any - previousOutputValue: number -}> { - const previousOutpoint = transaction.inputs[inputIndex].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - - const walletPublicKey = walletKeyRing.getPublicKey("hex") - if ( - BitcoinHashUtils.computeHash160(walletKeyRing.getPublicKey("hex")) != - deposit.walletPublicKeyHash - ) { - throw new Error( - "Wallet public key does not correspond to wallet private key" - ) - } - - if (!BitcoinPublicKeyUtils.isCompressedPublicKey(walletPublicKey)) { - throw new Error("Wallet public key must be compressed") - } - - const depositScript = bcoin.Script.fromRaw( - Buffer.from(await DepositScript.fromReceipt(deposit).getPlainText(), "hex") - ) - - return { - walletPublicKey, - depositScript: depositScript, - previousOutputValue: previousOutput.value, - } -} - -/** - * Prepares the proof of a deposit sweep transaction and submits it to the - * Bridge on-chain contract. - * @param transactionHash - Hash of the transaction being proven. - * @param mainUtxo - Recent main UTXO of the wallet as currently known on-chain. - * @param bridge - Handle to the Bridge on-chain contract. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @param vault - (Optional) The vault pointed by swept deposits. - * @returns Empty promise. - */ -export async function submitDepositSweepProof( - transactionHash: BitcoinTxHash, - mainUtxo: BitcoinUtxo, - bridge: Bridge, - bitcoinClient: BitcoinClient, - vault?: ChainIdentifier -): Promise { - const confirmations = await bridge.txProofDifficultyFactor() - const proof = await assembleBitcoinSpvProof( - transactionHash, - confirmations, - bitcoinClient - ) - // TODO: Write a converter and use it to convert the transaction part of the - // proof to the decomposed transaction data (version, inputs, outputs, locktime). - // Use raw transaction data for now. - const rawTransaction = await bitcoinClient.getRawTransaction(transactionHash) - const rawTransactionVectors = extractBitcoinRawTxVectors(rawTransaction) - await bridge.submitDepositSweepProof( - rawTransactionVectors, - proof, - mainUtxo, - vault - ) -} diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 1c50e4168..197a34d8f 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -1,55 +1,9 @@ -// TODO: Consider exports refactoring as per discussion https://github.com/keep-network/tbtc-v2/pull/460#discussion_r1084530007 - -import { validateBitcoinSpvProof } from "./lib/bitcoin" - -import { submitDepositSweepProof } from "./deposit-sweep" - -import { submitRedemptionProof } from "./redemption" - -import { - requestOptimisticMint, - cancelOptimisticMint, - finalizeOptimisticMint, - getOptimisticMintingRequest, -} from "./optimistic-minting" - -export const SpvMaintainer = { - submitDepositSweepProof, - submitRedemptionProof, -} - -export const OptimisticMinting = { - requestOptimisticMint, - cancelOptimisticMint, - finalizeOptimisticMint, - getOptimisticMintingRequest, -} - -export const Bitcoin = { - validateBitcoinSpvProof, -} - -export { - BitcoinTxHash, - BitcoinTx, - BitcoinTxOutput, - BitcoinLocktimeUtils, - BitcoinNetwork, -} from "./lib/bitcoin" - -export { ElectrumClient } from "./lib/electrum" - -export { - EthereumBridge, - EthereumWalletRegistry, - EthereumAddress, - EthereumTBTCVault, - EthereumTBTCToken, -} from "./lib/ethereum" - -export { Hex } from "./lib/utils" - -export { - OptimisticMintingRequest, - OptimisticMintingRequestedEvent, -} from "./lib/contracts" +export * from "./lib/bitcoin" +export * from "./lib/contracts" +export * from "./lib/electrum" +export * from "./lib/ethereum" +export * from "./lib/utils" + +export * from "./services/deposits" +export * from "./services/maintenance" +export * from "./services/redemptions" diff --git a/typescript/src/optimistic-minting.ts b/typescript/src/optimistic-minting.ts deleted file mode 100644 index 3f884e8ab..000000000 --- a/typescript/src/optimistic-minting.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { BitcoinTxHash } from "./lib/bitcoin" -import { TBTCVault, OptimisticMintingRequest } from "./lib/contracts" -import { Hex } from "./lib/utils" - -/** - * Requests optimistic minting for a deposit on chain. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @param tbtcVault Handle to the TBTCVault on-chain contract - * @returns Transaction hash of the optimistic mint request transaction. - */ -export async function requestOptimisticMint( - depositTxHash: BitcoinTxHash, - depositOutputIndex: number, - tbtcVault: TBTCVault -): Promise { - return await tbtcVault.requestOptimisticMint( - depositTxHash, - depositOutputIndex - ) -} - -/** - * Cancels optimistic minting for a deposit on chain. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @param tbtcVault Handle to the TBTCVault on-chain contract - * @returns Transaction hash of the optimistic mint cancel transaction. - */ -export async function cancelOptimisticMint( - depositTxHash: BitcoinTxHash, - depositOutputIndex: number, - tbtcVault: TBTCVault -): Promise { - return await tbtcVault.cancelOptimisticMint(depositTxHash, depositOutputIndex) -} - -/** - * Finalizes optimistic minting for a deposit on chain. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @param tbtcVault Handle to the TBTCVault on-chain contract - * @returns Transaction hash of the optimistic mint finalize transaction. - */ -export async function finalizeOptimisticMint( - depositTxHash: BitcoinTxHash, - depositOutputIndex: number, - tbtcVault: TBTCVault -): Promise { - return await tbtcVault.finalizeOptimisticMint( - depositTxHash, - depositOutputIndex - ) -} - -/** - * Gets optimistic minting request for a deposit from chain. - * @param depositTxHash The revealed deposit transaction's hash. - * @param depositOutputIndex Index of the deposit transaction output that - * funds the revealed deposit. - * @param tbtcVault Handle to the TBTCVault on-chain contract - * @returns Optimistic minting request. - */ -export async function getOptimisticMintingRequest( - depositTxHash: BitcoinTxHash, - depositOutputIndex: number, - tbtcVault: TBTCVault -): Promise { - return await tbtcVault.optimisticMintingRequests( - depositTxHash, - depositOutputIndex - ) -} diff --git a/typescript/src/redemption.ts b/typescript/src/redemption.ts deleted file mode 100644 index 5c1696812..000000000 --- a/typescript/src/redemption.ts +++ /dev/null @@ -1,256 +0,0 @@ -import bcoin from "bcoin" -import { BigNumber } from "ethers" -import { - assembleBitcoinSpvProof, - BitcoinPrivateKeyUtils, - extractBitcoinRawTxVectors, - BitcoinRawTx, - BitcoinUtxo, - BitcoinClient, - BitcoinTxHash, -} from "./lib/bitcoin" -import { Bridge, RedemptionRequest } from "./lib/contracts" - -/** - * Handles pending redemption requests by creating a redemption transaction - * transferring Bitcoins from the wallet's main UTXO to the provided redeemer - * output scripts and broadcasting it. The change UTXO resulting from the - * transaction becomes the new main UTXO of the wallet. - * @dev It is up to the caller to ensure the wallet key and each of the redeemer - * output scripts represent a valid pending redemption request in the Bridge. - * If this is not the case, an exception will be thrown. - * @param bitcoinClient - The Bitcoin client used to interact with the network - * @param bridge - The handle to the Bridge on-chain contract - * @param walletPrivateKey - The private kay of the wallet in the WIF format - * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO - * held by the on-chain Bridge contract - * @param redeemerOutputScripts - The list of output scripts that the redeemed - * funds will be locked to. The output scripts must be un-prefixed and - * not prepended with length - * @param witness - The parameter used to decide about the type of the change - * output. P2WPKH if `true`, P2PKH if `false` - * @returns The outcome consisting of: - * - the redemption transaction hash, - * - the optional new wallet's main UTXO produced by this transaction. - */ -export async function submitRedemptionTransaction( - bitcoinClient: BitcoinClient, - bridge: Bridge, - walletPrivateKey: string, - mainUtxo: BitcoinUtxo, - redeemerOutputScripts: string[], - witness: boolean -): Promise<{ - transactionHash: BitcoinTxHash - newMainUtxo?: BitcoinUtxo -}> { - const mainUtxoRawTransaction = await bitcoinClient.getRawTransaction( - mainUtxo.transactionHash - ) - - const mainUtxoWithRaw: BitcoinUtxo & BitcoinRawTx = { - ...mainUtxo, - transactionHex: mainUtxoRawTransaction.transactionHex, - } - - const walletPublicKey = BitcoinPrivateKeyUtils.createKeyRing(walletPrivateKey) - .getPublicKey() - .toString("hex") - - const redemptionRequests: RedemptionRequest[] = [] - - for (const redeemerOutputScript of redeemerOutputScripts) { - const redemptionRequest = await bridge.pendingRedemptions( - walletPublicKey, - redeemerOutputScript - ) - - if (redemptionRequest.requestedAt == 0) { - throw new Error("Redemption request does not exist") - } - - redemptionRequests.push({ - ...redemptionRequest, - redeemerOutputScript: redeemerOutputScript, - }) - } - - const { transactionHash, newMainUtxo, rawTransaction } = - await assembleRedemptionTransaction( - walletPrivateKey, - mainUtxoWithRaw, - redemptionRequests, - witness - ) - - // Note that `broadcast` may fail silently (i.e. no error will be returned, - // even if the transaction is rejected by other nodes and does not enter the - // mempool, for example due to an UTXO being already spent). - await bitcoinClient.broadcast(rawTransaction) - - return { transactionHash, newMainUtxo } -} - -/** - * Assembles a Bitcoin redemption transaction. - * The transaction will have a single input (main UTXO of the wallet making - * the redemption), an output for each redemption request provided, and a change - * output if the redemption requests do not consume the entire amount of the - * single input. - * @dev The caller is responsible for ensuring the redemption request list is - * correctly formed: - * - there is at least one redemption - * - the `requestedAmount` in each redemption request is greater than - * the sum of its `txFee` and `treasuryFee` - * @param walletPrivateKey - The private key of the wallet in the WIF format - * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO held - * by the on-chain Bridge contract - * @param redemptionRequests - The list of redemption requests - * @param witness - The parameter used to decide the type of the change output. - * P2WPKH if `true`, P2PKH if `false` - * @returns The outcome consisting of: - * - the redemption transaction hash, - * - the optional new wallet's main UTXO produced by this transaction. - * - the redemption transaction in the raw format - */ -export async function assembleRedemptionTransaction( - walletPrivateKey: string, - mainUtxo: BitcoinUtxo & BitcoinRawTx, - redemptionRequests: RedemptionRequest[], - witness: boolean -): Promise<{ - transactionHash: BitcoinTxHash - newMainUtxo?: BitcoinUtxo - rawTransaction: BitcoinRawTx -}> { - if (redemptionRequests.length < 1) { - throw new Error("There must be at least one request to redeem") - } - - const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( - walletPrivateKey, - witness - ) - const walletAddress = walletKeyRing.getAddress("string") - - // Use the main UTXO as the single transaction input - const inputCoins = [ - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), - mainUtxo.outputIndex, - -1 - ), - ] - - const transaction = new bcoin.MTX() - - let txTotalFee = BigNumber.from(0) - let totalOutputsValue = BigNumber.from(0) - - // Process the requests - for (const request of redemptionRequests) { - // Calculate the value of the output by subtracting tx fee and treasury - // fee for this particular output from the requested amount - const outputValue = request.requestedAmount - .sub(request.txMaxFee) - .sub(request.treasuryFee) - - // Add the output value to the total output value - totalOutputsValue = totalOutputsValue.add(outputValue) - - // Add the fee for this particular request to the overall transaction fee - // TODO: Using the maximum allowed transaction fee for the request (`txMaxFee`) - // as the transaction fee for now. In the future allow the caller to - // propose the value of the transaction fee. If the proposed transaction - // fee is smaller than the sum of fee shares from all the outputs then - // use the proposed fee and add the difference to outputs proportionally. - txTotalFee = txTotalFee.add(request.txMaxFee) - - transaction.addOutput({ - script: bcoin.Script.fromRaw( - Buffer.from(request.redeemerOutputScript, "hex") - ), - value: outputValue.toNumber(), - }) - } - - // If there is a change output, add it explicitly to the transaction. - // If we did not add this output explicitly, the bcoin library would add it - // anyway during funding, but if the value of the change output was very low, - // the library would consider it "dust" and add it to the fee rather than - // create a new output. - const changeOutputValue = mainUtxo.value - .sub(totalOutputsValue) - .sub(txTotalFee) - if (changeOutputValue.gt(0)) { - transaction.addOutput({ - script: bcoin.Script.fromAddress(walletAddress), - value: changeOutputValue.toNumber(), - }) - } - - await transaction.fund(inputCoins, { - changeAddress: walletAddress, - hardFee: txTotalFee.toNumber(), - subtractFee: false, - }) - - transaction.sign(walletKeyRing) - - const transactionHash = BitcoinTxHash.from(transaction.txid()) - // If there is a change output, it will be the new wallet's main UTXO. - const newMainUtxo = changeOutputValue.gt(0) - ? { - transactionHash, - // It was the last output added to the transaction. - outputIndex: transaction.outputs.length - 1, - value: changeOutputValue, - } - : undefined - - return { - transactionHash, - newMainUtxo, - rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), - }, - } -} - -/** - * Prepares the proof of a redemption transaction and submits it to the - * Bridge on-chain contract. - * @param transactionHash - Hash of the transaction being proven. - * @param mainUtxo - Recent main UTXO of the wallet as currently known on-chain. - * @param walletPublicKey - Bitcoin public key of the wallet. Must be in the - * compressed form (33 bytes long with 02 or 03 prefix). - * @param bridge - Handle to the Bridge on-chain contract. - * @param bitcoinClient - Bitcoin client used to interact with the network. - * @returns Empty promise. - */ -export async function submitRedemptionProof( - transactionHash: BitcoinTxHash, - mainUtxo: BitcoinUtxo, - walletPublicKey: string, - bridge: Bridge, - bitcoinClient: BitcoinClient -): Promise { - const confirmations = await bridge.txProofDifficultyFactor() - const proof = await assembleBitcoinSpvProof( - transactionHash, - confirmations, - bitcoinClient - ) - // TODO: Write a converter and use it to convert the transaction part of the - // proof to the decomposed transaction data (version, inputs, outputs, locktime). - // Use raw transaction data for now. - const rawTransaction = await bitcoinClient.getRawTransaction(transactionHash) - const rawTransactionVectors = extractBitcoinRawTxVectors(rawTransaction) - - await bridge.submitRedemptionProof( - rawTransactionVectors, - proof, - mainUtxo, - walletPublicKey - ) -} diff --git a/typescript/src/services/deposits/index.ts b/typescript/src/services/deposits/index.ts index f75596a11..d326e4023 100644 --- a/typescript/src/services/deposits/index.ts +++ b/typescript/src/services/deposits/index.ts @@ -1,3 +1,4 @@ export * from "./deposit" export * from "./deposits-service" export * from "./funding" +export * from "./refund" diff --git a/typescript/src/services/maintenance/index.ts b/typescript/src/services/maintenance/index.ts new file mode 100644 index 000000000..ee3f28e26 --- /dev/null +++ b/typescript/src/services/maintenance/index.ts @@ -0,0 +1,4 @@ +export * from "./maintenance-service" +export * from "./optimistic-minting" +export * from "./spv" +export * from "./wallet-tx" diff --git a/typescript/src/services/maintenance/maintenance-service.ts b/typescript/src/services/maintenance/maintenance-service.ts new file mode 100644 index 000000000..01c55637e --- /dev/null +++ b/typescript/src/services/maintenance/maintenance-service.ts @@ -0,0 +1,25 @@ +import { TBTCContracts } from "../../lib/contracts" +import { BitcoinClient } from "../../lib/bitcoin" +import { OptimisticMinting } from "./optimistic-minting" +import { Spv } from "./spv" +import {WalletTx} from "./wallet-tx"; + +/** + * Service exposing features relevant to authorized maintainers and + * operators of the tBTC v2 system. + */ +export class MaintenanceService { + /** + * Features for optimistic minting maintainers. + */ + public readonly optimisticMinting: OptimisticMinting + /** + * Features for SPV proof maintainers. + */ + public readonly spv: Spv + + constructor(tbtcContracts: TBTCContracts, bitcoinClient: BitcoinClient) { + this.optimisticMinting = new OptimisticMinting(tbtcContracts) + this.spv = new Spv(tbtcContracts, bitcoinClient) + } +} diff --git a/typescript/src/services/maintenance/optimistic-minting.ts b/typescript/src/services/maintenance/optimistic-minting.ts new file mode 100644 index 000000000..56c072a46 --- /dev/null +++ b/typescript/src/services/maintenance/optimistic-minting.ts @@ -0,0 +1,80 @@ +import { BitcoinTxHash } from "../../lib/bitcoin" +import { OptimisticMintingRequest } from "../../lib/contracts" +import { Hex } from "../../lib/utils" +import { TBTCContracts } from "../../lib/contracts" + +export class OptimisticMinting { + private readonly tbtcContracts: TBTCContracts + + constructor(tbtcContracts: TBTCContracts) { + this.tbtcContracts = tbtcContracts + } + + /** + * Requests optimistic minting for a deposit on chain. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Transaction hash of the optimistic mint request transaction. + */ + async requestMint( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + return this.tbtcContracts.tbtcVault.requestOptimisticMint( + depositTxHash, + depositOutputIndex + ) + } + + /** + * Cancels optimistic minting for a deposit on chain. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Transaction hash of the optimistic mint cancel transaction. + */ + async cancelMint( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + return this.tbtcContracts.tbtcVault.cancelOptimisticMint( + depositTxHash, + depositOutputIndex + ) + } + + /** + * Finalizes optimistic minting for a deposit on chain. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Transaction hash of the optimistic mint finalize transaction. + */ + async finalizeMint( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + return this.tbtcContracts.tbtcVault.finalizeOptimisticMint( + depositTxHash, + depositOutputIndex + ) + } + + /** + * Gets optimistic minting request for a deposit from chain. + * @param depositTxHash The revealed deposit transaction's hash. + * @param depositOutputIndex Index of the deposit transaction output that + * funds the revealed deposit. + * @returns Optimistic minting request. + */ + async getMintingRequest( + depositTxHash: BitcoinTxHash, + depositOutputIndex: number + ): Promise { + return this.tbtcContracts.tbtcVault.optimisticMintingRequests( + depositTxHash, + depositOutputIndex + ) + } +} diff --git a/typescript/src/services/maintenance/spv.ts b/typescript/src/services/maintenance/spv.ts new file mode 100644 index 000000000..d64fef5b9 --- /dev/null +++ b/typescript/src/services/maintenance/spv.ts @@ -0,0 +1,96 @@ +import { + assembleBitcoinSpvProof, + BitcoinClient, + BitcoinTxHash, + BitcoinUtxo, + extractBitcoinRawTxVectors, +} from "../../lib/bitcoin" +import { ChainIdentifier, TBTCContracts } from "../../lib/contracts" + +export class Spv { + /** + * Handle to tBTC contracts. + */ + private readonly tbtcContracts: TBTCContracts + /** + * Bitcoin client handle. + */ + private readonly bitcoinClient: BitcoinClient + + constructor(tbtcContracts: TBTCContracts, bitcoinClient: BitcoinClient) { + this.tbtcContracts = tbtcContracts + this.bitcoinClient = bitcoinClient + } + + /** + * Prepares the proof of a deposit sweep transaction and submits it to the + * Bridge on-chain contract. + * @param transactionHash - Hash of the transaction being proven. + * @param mainUtxo - Recent main UTXO of the wallet as currently known on-chain. + * @param vault - (Optional) The vault pointed by swept deposits. + * @returns Empty promise. + */ + async submitDepositSweepProof( + transactionHash: BitcoinTxHash, + mainUtxo: BitcoinUtxo, + vault?: ChainIdentifier + ): Promise { + const confirmations = + await this.tbtcContracts.bridge.txProofDifficultyFactor() + const proof = await assembleBitcoinSpvProof( + transactionHash, + confirmations, + this.bitcoinClient + ) + // TODO: Write a converter and use it to convert the transaction part of the + // proof to the decomposed transaction data (version, inputs, outputs, locktime). + // Use raw transaction data for now. + const rawTransaction = await this.bitcoinClient.getRawTransaction( + transactionHash + ) + const rawTransactionVectors = extractBitcoinRawTxVectors(rawTransaction) + await this.tbtcContracts.bridge.submitDepositSweepProof( + rawTransactionVectors, + proof, + mainUtxo, + vault + ) + } + + /** + * Prepares the proof of a redemption transaction and submits it to the + * Bridge on-chain contract. + * @param transactionHash - Hash of the transaction being proven. + * @param mainUtxo - Recent main UTXO of the wallet as currently known on-chain. + * @param walletPublicKey - Bitcoin public key of the wallet. Must be in the + * compressed form (33 bytes long with 02 or 03 prefix). + * @returns Empty promise. + */ + async submitRedemptionProof( + transactionHash: BitcoinTxHash, + mainUtxo: BitcoinUtxo, + walletPublicKey: string + ): Promise { + const confirmations = + await this.tbtcContracts.bridge.txProofDifficultyFactor() + const proof = await assembleBitcoinSpvProof( + transactionHash, + confirmations, + this.bitcoinClient + ) + // TODO: Write a converter and use it to convert the transaction part of the + // proof to the decomposed transaction data (version, inputs, outputs, locktime). + // Use raw transaction data for now. + const rawTransaction = await this.bitcoinClient.getRawTransaction( + transactionHash + ) + const rawTransactionVectors = extractBitcoinRawTxVectors(rawTransaction) + + await this.tbtcContracts.bridge.submitRedemptionProof( + rawTransactionVectors, + proof, + mainUtxo, + walletPublicKey + ) + } +} diff --git a/typescript/src/services/maintenance/wallet-tx.ts b/typescript/src/services/maintenance/wallet-tx.ts new file mode 100644 index 000000000..4c2522e98 --- /dev/null +++ b/typescript/src/services/maintenance/wallet-tx.ts @@ -0,0 +1,663 @@ +import { + BitcoinClient, + BitcoinHashUtils, + BitcoinPrivateKeyUtils, + BitcoinPublicKeyUtils, + BitcoinRawTx, + BitcoinTxHash, + BitcoinUtxo, +} from "../../lib/bitcoin" +import { BigNumber } from "ethers" +import { + DepositReceipt, + RedemptionRequest, + TBTCContracts, +} from "../../lib/contracts" +import bcoin from "bcoin" +import { DepositScript } from "../deposits" + +/** + * Wallet transactions builder. This feature set is supposed to be used only + * for internal purposes like system tests. In real world, tBTC v2 wallets + * are formed by peer-to-peer network participants that sign transactions + * using threshold signature schemes. + * + * @experimental THIS IS EXPERIMENTAL CODE THAT CAN BE CHANGED OR REMOVED + * IN FUTURE RELEASES. IT SHOULD BE USED ONLY FOR INTERNAL + * PURPOSES AND EXTERNAL APPLICATIONS SHOULD NOT DEPEND ON IT. + */ +// TODO: Abstract away transaction signing so there is no need to deal with +// private key directly. +export class WalletTx { + public readonly depositSweep: DepositSweep + public readonly redemption: Redemption + + constructor( + tbtcContracts: TBTCContracts, + bitcoinClient: BitcoinClient, + witness: boolean = true + ) { + this.depositSweep = new DepositSweep(bitcoinClient, witness) + this.redemption = new Redemption(tbtcContracts, bitcoinClient, witness) + } +} + +class DepositSweep { + /** + * Bitcoin client handle. + */ + private readonly bitcoinClient: BitcoinClient + /** + * Flag indicating whether the generated Bitcoin deposit sweep transaction + * should be a witness one. + */ + private readonly witness: boolean + + constructor(bitcoinClient: BitcoinClient, witness: boolean = true) { + this.bitcoinClient = bitcoinClient + this.witness = witness + } + + /** + * Submits a deposit sweep by combining all the provided P2(W)SH UTXOs and + * broadcasting a Bitcoin P2(W)PKH deposit sweep transaction. + * @dev The caller is responsible for ensuring the provided UTXOs are correctly + * formed, can be spent by the wallet and their combined value is greater + * than the fee. Note that broadcasting transaction may fail silently (e.g. + * when the provided UTXOs are not spendable) and no error will be returned. + * @param fee - the value that should be subtracted from the sum of the UTXOs + * values and used as the transaction fee. + * @param walletPrivateKey - Bitcoin private key of the wallet in WIF format. + * @param utxos - P2(W)SH UTXOs to be combined into one output. + * @param deposits - Array of deposits. Each element corresponds to UTXO. + * The number of UTXOs and deposit elements must equal. + * @param mainUtxo - main UTXO of the wallet, which is a P2WKH UTXO resulting + * from the previous wallet transaction (optional). + * @returns The outcome consisting of: + * - the sweep transaction hash, + * - the new wallet's main UTXO produced by this transaction. + */ + async submitTransaction( + fee: BigNumber, + walletPrivateKey: string, + utxos: BitcoinUtxo[], + deposits: DepositReceipt[], + mainUtxo?: BitcoinUtxo + ): Promise<{ + transactionHash: BitcoinTxHash + newMainUtxo: BitcoinUtxo + }> { + const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] + for (const utxo of utxos) { + const utxoRawTransaction = await this.bitcoinClient.getRawTransaction( + utxo.transactionHash + ) + + utxosWithRaw.push({ + ...utxo, + transactionHex: utxoRawTransaction.transactionHex, + }) + } + + let mainUtxoWithRaw + + if (mainUtxo) { + const mainUtxoRawTransaction = await this.bitcoinClient.getRawTransaction( + mainUtxo.transactionHash + ) + mainUtxoWithRaw = { + ...mainUtxo, + transactionHex: mainUtxoRawTransaction.transactionHex, + } + } + + const { transactionHash, newMainUtxo, rawTransaction } = + await this.assembleTransaction( + fee, + walletPrivateKey, + utxosWithRaw, + deposits, + mainUtxoWithRaw + ) + + // Note that `broadcast` may fail silently (i.e. no error will be returned, + // even if the transaction is rejected by other nodes and does not enter the + // mempool, for example due to an UTXO being already spent). + await this.bitcoinClient.broadcast(rawTransaction) + + return { transactionHash, newMainUtxo } + } + + /** + * Assembles a Bitcoin P2WPKH deposit sweep transaction. + * @dev The caller is responsible for ensuring the provided UTXOs are correctly + * formed, can be spent by the wallet and their combined value is greater + * then the fee. + * @param fee - the value that should be subtracted from the sum of the UTXOs + * values and used as the transaction fee. + * @param walletPrivateKey - Bitcoin private key of the wallet in WIF format. + * @param utxos - UTXOs from new deposit transactions. Must be P2(W)SH. + * @param deposits - Array of deposits. Each element corresponds to UTXO. + * The number of UTXOs and deposit elements must equal. + * @param mainUtxo - main UTXO of the wallet, which is a P2WKH UTXO resulting + * from the previous wallet transaction (optional). + * @returns The outcome consisting of: + * - the sweep transaction hash, + * - the new wallet's main UTXO produced by this transaction. + * - the sweep transaction in the raw format + */ + async assembleTransaction( + fee: BigNumber, + walletPrivateKey: string, + utxos: (BitcoinUtxo & BitcoinRawTx)[], + deposits: DepositReceipt[], + mainUtxo?: BitcoinUtxo & BitcoinRawTx + ): Promise<{ + transactionHash: BitcoinTxHash + newMainUtxo: BitcoinUtxo + rawTransaction: BitcoinRawTx + }> { + if (utxos.length < 1) { + throw new Error("There must be at least one deposit UTXO to sweep") + } + + if (utxos.length != deposits.length) { + throw new Error( + "Number of UTXOs must equal the number of deposit elements" + ) + } + + const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( + walletPrivateKey, + this.witness + ) + const walletAddress = walletKeyRing.getAddress("string") + + const inputCoins = [] + let totalInputValue = BigNumber.from(0) + + if (mainUtxo) { + inputCoins.push( + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), + mainUtxo.outputIndex, + -1 + ) + ) + totalInputValue = totalInputValue.add(mainUtxo.value) + } + + for (const utxo of utxos) { + inputCoins.push( + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), + utxo.outputIndex, + -1 + ) + ) + totalInputValue = totalInputValue.add(utxo.value) + } + + const transaction = new bcoin.MTX() + + transaction.addOutput({ + script: bcoin.Script.fromAddress(walletAddress), + value: totalInputValue.toNumber(), + }) + + await transaction.fund(inputCoins, { + changeAddress: walletAddress, + hardFee: fee.toNumber(), + subtractFee: true, + }) + + if (transaction.outputs.length != 1) { + throw new Error("Deposit sweep transaction must have only one output") + } + + // UTXOs must be mapped to deposits, as `fund` may arrange inputs in any + // order + const utxosWithDeposits: (BitcoinUtxo & BitcoinRawTx & DepositReceipt)[] = + utxos.map((utxo, index) => ({ + ...utxo, + ...deposits[index], + })) + + for (let i = 0; i < transaction.inputs.length; i++) { + const previousOutpoint = transaction.inputs[i].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + const previousScript = previousOutput.script + + // P2(W)PKH (main UTXO) + if ( + previousScript.isPubkeyhash() || + previousScript.isWitnessPubkeyhash() + ) { + await this.signMainUtxoInput(transaction, i, walletKeyRing) + continue + } + + const utxoWithDeposit = utxosWithDeposits.find( + (u) => + u.transactionHash.toString() === previousOutpoint.txid() && + u.outputIndex == previousOutpoint.index + ) + if (!utxoWithDeposit) { + throw new Error("Unknown input") + } + + if (previousScript.isScripthash()) { + // P2SH (deposit UTXO) + await this.signP2SHDepositInput( + transaction, + i, + utxoWithDeposit, + walletKeyRing + ) + } else if (previousScript.isWitnessScripthash()) { + // P2WSH (deposit UTXO) + await this.signP2WSHDepositInput( + transaction, + i, + utxoWithDeposit, + walletKeyRing + ) + } else { + throw new Error("Unsupported UTXO script type") + } + } + + const transactionHash = BitcoinTxHash.from(transaction.txid()) + + return { + transactionHash, + newMainUtxo: { + transactionHash, + outputIndex: 0, // There is only one output. + value: BigNumber.from(transaction.outputs[0].value), + }, + rawTransaction: { + transactionHex: transaction.toRaw().toString("hex"), + }, + } + } + + /** + * Creates script for the transaction input at the given index and signs the + * input. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Empty promise. + */ + private async signMainUtxoInput( + transaction: any, + inputIndex: number, + walletKeyRing: any + ) { + const previousOutpoint = transaction.inputs[inputIndex].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + if (!walletKeyRing.ownOutput(previousOutput)) { + throw new Error("UTXO does not belong to the wallet") + } + // Build script and set it as input's witness + transaction.scriptInput(inputIndex, previousOutput, walletKeyRing) + // Build signature and add it in front of script in input's witness + transaction.signInput(inputIndex, previousOutput, walletKeyRing) + } + + /** + * Creates and sets `scriptSig` for the transaction input at the given index by + * combining signature, wallet public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param deposit - Data of the deposit. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Empty promise. + */ + private async signP2SHDepositInput( + transaction: any, + inputIndex: number, + deposit: DepositReceipt, + walletKeyRing: any + ): Promise { + const { walletPublicKey, depositScript, previousOutputValue } = + await this.prepareInputSignData( + transaction, + inputIndex, + deposit, + walletKeyRing + ) + + const signature: Buffer = transaction.signature( + inputIndex, + depositScript, + previousOutputValue, + walletKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 0 // legacy sighash version + ) + const scriptSig = new bcoin.Script() + scriptSig.clear() + scriptSig.pushData(signature) + scriptSig.pushData(Buffer.from(walletPublicKey, "hex")) + scriptSig.pushData(depositScript.toRaw()) + scriptSig.compile() + + transaction.inputs[inputIndex].script = scriptSig + } + + /** + * Creates and sets witness script for the transaction input at the given index + * by combining signature, wallet public key and deposit script. + * @param transaction - Mutable transaction containing the input to be signed. + * @param inputIndex - Index that points to the input to be signed. + * @param deposit - Data of the deposit. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Empty promise. + */ + private async signP2WSHDepositInput( + transaction: any, + inputIndex: number, + deposit: DepositReceipt, + walletKeyRing: any + ): Promise { + const { walletPublicKey, depositScript, previousOutputValue } = + await this.prepareInputSignData( + transaction, + inputIndex, + deposit, + walletKeyRing + ) + + const signature: Buffer = transaction.signature( + inputIndex, + depositScript, + previousOutputValue, + walletKeyRing.privateKey, + bcoin.Script.hashType.ALL, + 1 // segwit sighash version + ) + + const witness = new bcoin.Witness() + witness.clear() + witness.pushData(signature) + witness.pushData(Buffer.from(walletPublicKey, "hex")) + witness.pushData(depositScript.toRaw()) + witness.compile() + + transaction.inputs[inputIndex].witness = witness + } + + /** + * Creates data needed to sign a deposit input. + * @param transaction - Mutable transaction containing the input. + * @param inputIndex - Index that points to the input. + * @param deposit - Data of the deposit. + * @param walletKeyRing - Key ring created using the wallet's private key. + * @returns Data needed to sign the input. + */ + private async prepareInputSignData( + transaction: any, + inputIndex: number, + deposit: DepositReceipt, + walletKeyRing: any + ): Promise<{ + walletPublicKey: string + depositScript: any + previousOutputValue: number + }> { + const previousOutpoint = transaction.inputs[inputIndex].prevout + const previousOutput = transaction.view.getOutput(previousOutpoint) + + const walletPublicKey = walletKeyRing.getPublicKey("hex") + if ( + BitcoinHashUtils.computeHash160(walletKeyRing.getPublicKey("hex")) != + deposit.walletPublicKeyHash + ) { + throw new Error( + "Wallet public key does not correspond to wallet private key" + ) + } + + if (!BitcoinPublicKeyUtils.isCompressedPublicKey(walletPublicKey)) { + throw new Error("Wallet public key must be compressed") + } + + const depositScript = bcoin.Script.fromRaw( + Buffer.from( + await DepositScript.fromReceipt(deposit).getPlainText(), + "hex" + ) + ) + + return { + walletPublicKey, + depositScript: depositScript, + previousOutputValue: previousOutput.value, + } + } +} + +class Redemption { + /** + * Handle to tBTC contracts. + */ + private readonly tbtcContracts: TBTCContracts + /** + * Bitcoin client handle. + */ + private readonly bitcoinClient: BitcoinClient + /** + * Flag indicating whether the generated Bitcoin redemption transaction + * should be a witness one. + */ + private readonly witness: boolean + + constructor( + tbtcContracts: TBTCContracts, + bitcoinClient: BitcoinClient, + witness: boolean = true + ) { + this.tbtcContracts = tbtcContracts + this.bitcoinClient = bitcoinClient + this.witness = witness + } + /** + * Handles pending redemption requests by creating a redemption transaction + * transferring Bitcoins from the wallet's main UTXO to the provided redeemer + * output scripts and broadcasting it. The change UTXO resulting from the + * transaction becomes the new main UTXO of the wallet. + * @dev It is up to the caller to ensure the wallet key and each of the redeemer + * output scripts represent a valid pending redemption request in the Bridge. + * If this is not the case, an exception will be thrown. + * @param walletPrivateKey - The private kay of the wallet in the WIF format + * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO + * held by the on-chain Bridge contract + * @param redeemerOutputScripts - The list of output scripts that the redeemed + * funds will be locked to. The output scripts must be un-prefixed and + * not prepended with length + * @returns The outcome consisting of: + * - the redemption transaction hash, + * - the optional new wallet's main UTXO produced by this transaction. + */ + async submitTransaction( + walletPrivateKey: string, + mainUtxo: BitcoinUtxo, + redeemerOutputScripts: string[] + ): Promise<{ + transactionHash: BitcoinTxHash + newMainUtxo?: BitcoinUtxo + }> { + const mainUtxoRawTransaction = await this.bitcoinClient.getRawTransaction( + mainUtxo.transactionHash + ) + + const mainUtxoWithRaw: BitcoinUtxo & BitcoinRawTx = { + ...mainUtxo, + transactionHex: mainUtxoRawTransaction.transactionHex, + } + + const walletPublicKey = BitcoinPrivateKeyUtils.createKeyRing( + walletPrivateKey + ) + .getPublicKey() + .toString("hex") + + const redemptionRequests: RedemptionRequest[] = [] + + for (const redeemerOutputScript of redeemerOutputScripts) { + const redemptionRequest = + await this.tbtcContracts.bridge.pendingRedemptions( + walletPublicKey, + redeemerOutputScript + ) + + if (redemptionRequest.requestedAt == 0) { + throw new Error("Redemption request does not exist") + } + + redemptionRequests.push({ + ...redemptionRequest, + redeemerOutputScript: redeemerOutputScript, + }) + } + + const { transactionHash, newMainUtxo, rawTransaction } = + await this.assembleTransaction( + walletPrivateKey, + mainUtxoWithRaw, + redemptionRequests + ) + + // Note that `broadcast` may fail silently (i.e. no error will be returned, + // even if the transaction is rejected by other nodes and does not enter the + // mempool, for example due to an UTXO being already spent). + await this.bitcoinClient.broadcast(rawTransaction) + + return { transactionHash, newMainUtxo } + } + + /** + * Assembles a Bitcoin redemption transaction. + * The transaction will have a single input (main UTXO of the wallet making + * the redemption), an output for each redemption request provided, and a change + * output if the redemption requests do not consume the entire amount of the + * single input. + * @dev The caller is responsible for ensuring the redemption request list is + * correctly formed: + * - there is at least one redemption + * - the `requestedAmount` in each redemption request is greater than + * the sum of its `txFee` and `treasuryFee` + * @param walletPrivateKey - The private key of the wallet in the WIF format + * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO held + * by the on-chain Bridge contract + * @param redemptionRequests - The list of redemption requests + * @returns The outcome consisting of: + * - the redemption transaction hash, + * - the optional new wallet's main UTXO produced by this transaction. + * - the redemption transaction in the raw format + */ + async assembleTransaction( + walletPrivateKey: string, + mainUtxo: BitcoinUtxo & BitcoinRawTx, + redemptionRequests: RedemptionRequest[] + ): Promise<{ + transactionHash: BitcoinTxHash + newMainUtxo?: BitcoinUtxo + rawTransaction: BitcoinRawTx + }> { + if (redemptionRequests.length < 1) { + throw new Error("There must be at least one request to redeem") + } + + const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( + walletPrivateKey, + this.witness + ) + const walletAddress = walletKeyRing.getAddress("string") + + // Use the main UTXO as the single transaction input + const inputCoins = [ + bcoin.Coin.fromTX( + bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), + mainUtxo.outputIndex, + -1 + ), + ] + + const transaction = new bcoin.MTX() + + let txTotalFee = BigNumber.from(0) + let totalOutputsValue = BigNumber.from(0) + + // Process the requests + for (const request of redemptionRequests) { + // Calculate the value of the output by subtracting tx fee and treasury + // fee for this particular output from the requested amount + const outputValue = request.requestedAmount + .sub(request.txMaxFee) + .sub(request.treasuryFee) + + // Add the output value to the total output value + totalOutputsValue = totalOutputsValue.add(outputValue) + + // Add the fee for this particular request to the overall transaction fee + // TODO: Using the maximum allowed transaction fee for the request (`txMaxFee`) + // as the transaction fee for now. In the future allow the caller to + // propose the value of the transaction fee. If the proposed transaction + // fee is smaller than the sum of fee shares from all the outputs then + // use the proposed fee and add the difference to outputs proportionally. + txTotalFee = txTotalFee.add(request.txMaxFee) + + transaction.addOutput({ + script: bcoin.Script.fromRaw( + Buffer.from(request.redeemerOutputScript, "hex") + ), + value: outputValue.toNumber(), + }) + } + + // If there is a change output, add it explicitly to the transaction. + // If we did not add this output explicitly, the bcoin library would add it + // anyway during funding, but if the value of the change output was very low, + // the library would consider it "dust" and add it to the fee rather than + // create a new output. + const changeOutputValue = mainUtxo.value + .sub(totalOutputsValue) + .sub(txTotalFee) + if (changeOutputValue.gt(0)) { + transaction.addOutput({ + script: bcoin.Script.fromAddress(walletAddress), + value: changeOutputValue.toNumber(), + }) + } + + await transaction.fund(inputCoins, { + changeAddress: walletAddress, + hardFee: txTotalFee.toNumber(), + subtractFee: false, + }) + + transaction.sign(walletKeyRing) + + const transactionHash = BitcoinTxHash.from(transaction.txid()) + // If there is a change output, it will be the new wallet's main UTXO. + const newMainUtxo = changeOutputValue.gt(0) + ? { + transactionHash, + // It was the last output added to the transaction. + outputIndex: transaction.outputs.length - 1, + value: changeOutputValue, + } + : undefined + + return { + transactionHash, + newMainUtxo, + rawTransaction: { + transactionHex: transaction.toRaw().toString("hex"), + }, + } + } +} diff --git a/typescript/test/bitcoin-network.test.ts b/typescript/test/bitcoin-network.test.ts index aa87a5c0c..db4555b74 100644 --- a/typescript/test/bitcoin-network.test.ts +++ b/typescript/test/bitcoin-network.test.ts @@ -1,9 +1,5 @@ import { expect } from "chai" -import { - BitcoinTxHash, - BitcoinNetwork, - toBcoinNetwork, -} from "../src/lib/bitcoin" +import { BitcoinTxHash, BitcoinNetwork, toBcoinNetwork } from "../src" describe("BitcoinNetwork", () => { const testData = [ diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index 6b03fff4b..785875480 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -9,8 +9,8 @@ import { BitcoinTargetConverter, BitcoinCompactSizeUint, BitcoinAddressConverter, -} from "../src/lib/bitcoin" -import { Hex } from "../src/lib/utils" + Hex, +} from "../src" import { BigNumber } from "ethers" import { btcAddresses } from "./data/bitcoin" diff --git a/typescript/test/data/deposit-refund.ts b/typescript/test/data/deposit-refund.ts index 57a41762d..ff0782a2a 100644 --- a/typescript/test/data/deposit-refund.ts +++ b/typescript/test/data/deposit-refund.ts @@ -4,9 +4,9 @@ import { BitcoinUtxo, BitcoinTxHash, BitcoinLocktimeUtils, -} from "../../src/lib/bitcoin" -import { DepositReceipt } from "../../src/lib/contracts" -import { EthereumAddress } from "../../src/lib/ethereum" + DepositReceipt, + EthereumAddress, +} from "../../src" /** * Testnet private key that can be used to refund the deposits used in tests. diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index 14251228b..0c962b841 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -7,11 +7,11 @@ import { BitcoinTxMerkleBranch, BitcoinTxHash, BitcoinLocktimeUtils, -} from "../../src/lib/bitcoin" -import { DepositReceipt } from "../../src/lib/contracts" + DepositReceipt, + EthereumAddress, + Hex, +} from "../../src" import { BigNumber } from "ethers" -import { EthereumAddress } from "../../src/lib/ethereum" -import { Hex } from "../../src" export const NO_MAIN_UTXO = { transactionHash: BitcoinTxHash.from(""), diff --git a/typescript/test/data/deposit.ts b/typescript/test/data/deposit.ts index 241c6e4bd..16a2e1d00 100644 --- a/typescript/test/data/deposit.ts +++ b/typescript/test/data/deposit.ts @@ -1,4 +1,4 @@ -import { BitcoinRawTx, BitcoinTxHash, BitcoinUtxo } from "../../src/lib/bitcoin" +import { BitcoinRawTx, BitcoinTxHash, BitcoinUtxo } from "../../src" import { BigNumber } from "ethers" /** diff --git a/typescript/test/data/electrum.ts b/typescript/test/data/electrum.ts index 374fe9c14..22d930672 100644 --- a/typescript/test/data/electrum.ts +++ b/typescript/test/data/electrum.ts @@ -4,9 +4,9 @@ import { BitcoinUtxo, BitcoinTxMerkleBranch, BitcoinTxHash, -} from "../../src/lib/bitcoin" + Hex, +} from "../../src" import { BigNumber } from "ethers" -import { Hex } from "../../src" /** * Bitcoin testnet address used for Electrum client tests. diff --git a/typescript/test/data/proof.ts b/typescript/test/data/proof.ts index ec212b497..bbd94a853 100644 --- a/typescript/test/data/proof.ts +++ b/typescript/test/data/proof.ts @@ -4,9 +4,9 @@ import { BitcoinTx, BitcoinTxHash, BitcoinTxMerkleBranch, -} from "../../src/lib/bitcoin" + Hex, +} from "../../src" import { BigNumber } from "ethers" -import { Hex } from "../../src" /** * Represents a set of data used for given proof scenario. diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index c66863c50..9ff50845d 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -8,10 +8,11 @@ import { BitcoinTxMerkleBranch, BitcoinTxHash, BitcoinAddressConverter, -} from "../../src/lib/bitcoin" -import { RedemptionRequest, WalletState } from "../../src/lib/contracts" -import { EthereumAddress } from "../../src/lib/ethereum" -import { Hex } from "../../src" + RedemptionRequest, + WalletState, + EthereumAddress, + Hex, +} from "../../src" /** * Private key (testnet) of the wallet. diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts index b92c63b84..403d56020 100644 --- a/typescript/test/deposit-sweep.test.ts +++ b/typescript/test/deposit-sweep.test.ts @@ -4,7 +4,9 @@ import { BitcoinTxHash, BitcoinUtxo, BitcoinTx, -} from "../src/lib/bitcoin" + MaintenanceService, + WalletTx, +} from "../src" import { testnetDepositScripthashAddress, testnetDepositWitnessScripthashAddress, @@ -20,99 +22,299 @@ import { NO_MAIN_UTXO, } from "./data/deposit-sweep" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import { MockBridge } from "./utils/mock-bridge" import bcoin from "bcoin" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) import { expect } from "chai" -import { - assembleDepositSweepTransaction, - submitDepositSweepProof, - submitDepositSweepTransaction, -} from "../src/deposit-sweep" +import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" describe("Sweep", () => { const fee = BigNumber.from(1600) - describe("submitDepositSweepTransaction", () => { - let bitcoinClient: MockBitcoinClient + describe("WalletTx", () => { + describe("DepositSweep", () => { + describe("submitTransaction", () => { + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient - beforeEach(async () => { - bcoin.set("testnet") + beforeEach(async () => { + bcoin.set("testnet") - bitcoinClient = new MockBitcoinClient() - }) + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + }) - context("when the new main UTXO is requested to be witness", () => { - context("when there is no main UTXO from previous deposit sweep", () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo + context("when the new main UTXO is requested to be witness", () => { + context( + "when there is no main UTXO from previous deposit sweep", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithNoMainUtxoAndWitnessOutput.deposits) { + rawTransactions.set(deposit.utxo.transactionHash.toString(), { + transactionHex: deposit.utxo.transactionHex, + }) + } + bitcoinClient.rawTransactions = rawTransactions + + const utxos: BitcoinUtxo[] = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (data) => { + return data.utxo + } + ) + + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + const witness = + depositSweepWithNoMainUtxoAndWitnessOutput.witness + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithNoMainUtxoAndWitnessOutput.deposits) { - rawTransactions.set(deposit.utxo.transactionHash.toString(), { - transactionHex: deposit.utxo.transactionHex, - }) - } - bitcoinClient.rawTransactions = rawTransactions + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposit + )) + }) - const utxos: BitcoinUtxo[] = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map((data) => { - return data.utxo - }) + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transaction + ) + }) - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash, + outputIndex: 0, + value: BigNumber.from(35400), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context("when there is main UTXO from previous deposit sweep", () => { + context( + "when main UTXO from previous deposit sweep is witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits) { + rawTransactions.set( + deposit.utxo.transactionHash.toString(), + { + transactionHex: deposit.utxo.transactionHex, + } + ) + } + // The main UTXO resulting from another data set was used as input. + // Set raw data of that main UTXO as well. + rawTransactions.set( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString(), + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transaction + ) + bitcoinClient.rawTransactions = rawTransactions + + const utxos: BitcoinUtxo[] = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + const witness = + depositSweepWithWitnessMainUtxoAndWitnessOutput.witness + + const mainUtxo = + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposit, + mainUtxo + )) + }) + + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(60800), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) } ) - const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - ;({ transactionHash, newMainUtxo } = - await submitDepositSweepTransaction( - bitcoinClient, - fee, - testnetWalletPrivateKey, - witness, - utxos, - deposit - )) - }) + context( + "when main UTXO from previous deposit sweep is non-witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits) { + rawTransactions.set( + deposit.utxo.transactionHash.toString(), + { + transactionHex: deposit.utxo.transactionHex, + } + ) + } + rawTransactions.set( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString(), + { + transactionHex: + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .mainUtxo.transactionHex, + } + ) + bitcoinClient.rawTransactions = rawTransactions + + const utxos: BitcoinUtxo[] = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + const witness = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness + + const mainUtxo = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transaction - ) - }) + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposit, + mainUtxo + )) + }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash - ) - }) + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + }) - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(35400), - } + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(33800), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + }) }) - }) - context("when there is main UTXO from previous deposit sweep", () => { - context("when main UTXO from previous deposit sweep is witness", () => { + context("when the new main UTXO is requested to be non-witness", () => { + // The only difference between deposit sweep transactions with witness and + // non-witness output is the output type itself. + // Therefore only one test case was added for non-witness transactions. let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo @@ -120,63 +322,51 @@ describe("Sweep", () => { // Map transaction hashes for UTXOs to transactions in hexadecimal and // set the mapping in the mock Bitcoin client const rawTransactions = new Map() - for (const deposit of depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits) { + for (const deposit of depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits) { rawTransactions.set(deposit.utxo.transactionHash.toString(), { transactionHex: deposit.utxo.transactionHex, }) } - // The main UTXO resulting from another data set was used as input. - // Set raw data of that main UTXO as well. - rawTransactions.set( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString(), - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transaction - ) bitcoinClient.rawTransactions = rawTransactions - const utxos: BitcoinUtxo[] = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo + const utxos = + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( + (data) => { + return data.utxo } ) - const deposit = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + const deposits = + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( (deposit) => { return deposit.data } ) - const witness = - depositSweepWithWitnessMainUtxoAndWitnessOutput.witness + depositSweepWithNoMainUtxoAndNonWitnessOutput.witness - const mainUtxo = - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + const walletTx = new WalletTx(tbtcContracts, bitcoinClient, witness) ;({ transactionHash, newMainUtxo } = - await submitDepositSweepTransaction( - bitcoinClient, + await walletTx.depositSweep.submitTransaction( fee, testnetWalletPrivateKey, - witness, utxos, - deposit, - mainUtxo + deposits )) }) it("should broadcast sweep transaction with proper structure", async () => { expect(bitcoinClient.broadcastLog.length).to.be.equal(1) expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep .transaction ) }) it("should return the proper transaction hash", async () => { expect(transactionHash).to.be.deep.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep .transactionHash ) }) @@ -184,343 +374,503 @@ describe("Sweep", () => { it("should return the proper new main UTXO", () => { const expectedNewMainUtxo = { transactionHash: - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep .transactionHash, outputIndex: 0, - value: BigNumber.from(60800), + value: BigNumber.from(13400), } expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) }) }) + }) - context( - "when main UTXO from previous deposit sweep is non-witness", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits) { - rawTransactions.set(deposit.utxo.transactionHash.toString(), { - transactionHex: deposit.utxo.transactionHex, - }) - } - rawTransactions.set( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString(), - { - transactionHex: - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - .transactionHex, - } - ) - bitcoinClient.rawTransactions = rawTransactions + describe("assembleTransaction", () => { + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient - const utxos: BitcoinUtxo[] = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + }) + + context("when the new main UTXO is requested to be witness", () => { + context( + "when there is no main UTXO from previous deposit sweep", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (data) => { + return data.utxo } ) const deposit = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( (deposit) => { return deposit.data } ) - const witness = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness + const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - const mainUtxo = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) - ;({ transactionHash, newMainUtxo } = - await submitDepositSweepTransaction( - bitcoinClient, + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( fee, testnetWalletPrivateKey, - witness, - utxos, - deposit, - mainUtxo + utxosWithRaw, + deposit )) - }) - - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep - .transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep - .transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash, - outputIndex: 0, - value: BigNumber.from(33800), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - }) - }) + }) - context("when the new main UTXO is requested to be non-witness", () => { - // The only difference between deposit sweep transactions with witness and - // non-witness output is the output type itself. - // Therefore only one test case was added for non-witness transactions. - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transaction + ) - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits) { - rawTransactions.set(deposit.utxo.transactionHash.toString(), { - transactionHex: deposit.utxo.transactionHex, - }) - } - bitcoinClient.rawTransactions = rawTransactions + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - const utxos = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map((data) => { - return data.utxo - }) + expect(txJSON.hash).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) - const deposits = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - const witness = depositSweepWithNoMainUtxoAndNonWitnessOutput.witness - - ;({ transactionHash, newMainUtxo } = - await submitDepositSweepTransaction( - bitcoinClient, - fee, - testnetWalletPrivateKey, - witness, - utxos, - deposits - )) - }) + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(2) - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transaction - ) - }) + const p2shInput = txJSON.inputs[0] + expect(p2shInput.prevout.hash).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2shInput.prevout.index).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo + .outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2shInput.witness).to.be.equal("00") + expect(p2shInput.script.length).to.be.greaterThan(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2shInput.address).to.be.equal( + testnetDepositScripthashAddress + ) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash - ) - }) + const p2wshInput = txJSON.inputs[1] + expect(p2wshInput.prevout.hash).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() + ) + expect(p2wshInput.prevout.index).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo + .outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // witness script hash + expect(p2wshInput.address).to.be.equal( + testnetDepositWitnessScripthashAddress + ) - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(13400), - } + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) + const sweepOutput = txJSON.outputs[0] - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - }) - }) + // Should be OP_0 . Public key corresponds to the + // wallet BTC address. + expect(sweepOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + // The output's value should be equal to the sum of all input values + // minus fee (25000 + 12000 - 1600) + expect(sweepOutput.value).to.be.equal(35400) + }) - describe("assembleDepositSweepTransaction", () => { - context("when the new main UTXO is requested to be witness", () => { - context("when there is no main UTXO from previous deposit sweep", () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash + ) + }) - const utxosWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map((data) => { - return data.utxo - }) + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash, + outputIndex: 0, + value: BigNumber.from(35400), + } - const deposit = depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) - const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness + context("when there is main UTXO from previous deposit sweep", () => { + context( + "when main UTXO prom previous deposit sweep is witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + // P2WPKH + const mainUtxoWithRaw = + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + + const witness = + depositSweepWithWitnessMainUtxoAndWitnessOutput.witness + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - witness, - utxosWithRaw, - deposit - )) - }) + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit, + mainUtxoWithRaw + )) + }) - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transaction - ) + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + + expect(txJSON.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(3) + + const p2wkhInput = txJSON.inputs[0] + expect(p2wkhInput.prevout.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() + ) + expect(p2wkhInput.prevout.index).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + .outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wkhInput.witness.length).to.be.greaterThan(0) + expect(p2wkhInput.script.length).to.be.equal(0) + // The input comes from the main UTXO so the input should be the + // wallet's address + expect(p2wkhInput.address).to.be.equal(testnetWalletAddress) + + const p2shInput = txJSON.inputs[1] + expect(p2shInput.prevout.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2shInput.prevout.index).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0] + .utxo.outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2shInput.witness).to.be.equal("00") + expect(p2shInput.script.length).to.be.greaterThan(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2shInput.address).to.be.equal( + testnetDepositScripthashAddress + ) + + const p2wshInput = txJSON.inputs[2] + expect(p2wshInput.prevout.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() + ) + expect(p2wshInput.prevout.index).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1] + .utxo.outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // witness script hash + expect(p2wshInput.address).to.be.equal( + testnetDepositWitnessScripthashAddress + ) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) + + const sweepOutput = txJSON.outputs[0] + // Should be OP_0 . Public key corresponds to the + // wallet BTC address. + expect(sweepOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + // The output's value should be equal to the sum of all input values + // minus fee (17000 + 10000 + 35400 - 1600) + expect(sweepOutput.value).to.be.equal(60800) + }) - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) - expect(txJSON.hash).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(60800), + } - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(2) + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) - const p2shInput = txJSON.inputs[0] - expect(p2shInput.prevout.hash).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2shInput.prevout.index).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - .outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.equal("00") - expect(p2shInput.script.length).to.be.greaterThan(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2shInput.address).to.be.equal(testnetDepositScripthashAddress) - - const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.prevout.hash).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() - ) - expect(p2wshInput.prevout.index).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo - .outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // witness script hash - expect(p2wshInput.address).to.be.equal( - testnetDepositWitnessScripthashAddress - ) + context( + "when main UTXO from previous deposit sweep is non-witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + // P2WPKH + const mainUtxoWithRaw = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + + const witness = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) - const sweepOutput = txJSON.outputs[0] + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit, + mainUtxoWithRaw + )) + }) - // Should be OP_0 . Public key corresponds to the - // wallet BTC address. - expect(sweepOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal(testnetWalletAddress) - // The output's value should be equal to the sum of all input values - // minus fee (25000 + 12000 - 1600) - expect(sweepOutput.value).to.be.equal(35400) - }) + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + + expect(txJSON.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(2) + + const p2wshInput = txJSON.inputs[0] + expect(p2wshInput.prevout.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2wshInput.prevout.index).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .deposits[0].utxo.outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2wshInput.address).to.be.equal( + "tb1qk8urugnf08wfle6wslmdxq7mkz9z0gw8e6gkvspn7dx87tfpfntshdm7qr" + ) + + const p2pkhInput = txJSON.inputs[1] // main UTXO + expect(p2pkhInput.prevout.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() + ) + expect(p2pkhInput.prevout.index).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + .outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2pkhInput.witness).to.be.equal("00") + expect(p2pkhInput.script.length).to.be.greaterThan(0) + // The input comes from the main UTXO so the input should be the + // wallet's address + expect(p2pkhInput.address).to.be.equal( + "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" + ) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) + + const sweepOutput = txJSON.outputs[0] + // Should be OP_0 . Public key corresponds to the + // wallet BTC address. + expect(sweepOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + // The output's value should be equal to the sum of all input values + // minus fee (16400 + 19000 - 1600) + expect(sweepOutput.value).to.be.equal(33800) + }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash - ) - }) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(35400), - } + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(33800), + } - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + }) }) - }) - context("when there is main UTXO from previous deposit sweep", () => { - context("when main UTXO prom previous deposit sweep is witness", () => { + context("when the new main UTXO is requested to be non-witness", () => { + // The only difference between deposit sweep transactions with witness and + // non-witness output is the output type itself. + // Therefore only one test case was added for non-witness transactions. let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo let transaction: BitcoinRawTx const utxosWithRaw = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( (deposit) => { return deposit.utxo } ) const deposit = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( (deposit) => { return deposit.data } ) - // P2WPKH - const mainUtxoWithRaw = - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + const witness = depositSweepWithNoMainUtxoAndNonWitnessOutput.witness - const witness = - depositSweepWithWitnessMainUtxoAndWitnessOutput.witness + const walletTx = new WalletTx(tbtcContracts, bitcoinClient, witness) beforeEach(async () => { ;({ transactionHash, newMainUtxo, rawTransaction: transaction, - } = await assembleDepositSweepTransaction( + } = await walletTx.depositSweep.assembleTransaction( fee, testnetWalletPrivateKey, - witness, utxosWithRaw, - deposit, - mainUtxoWithRaw + deposit )) }) it("should return sweep transaction with proper structure", () => { // Compare HEXes. expect(transaction).to.be.eql( - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep .transaction ) @@ -529,35 +879,19 @@ describe("Sweep", () => { const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep.transactionHash.toString() ) expect(txJSON.version).to.be.equal(1) // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(3) + expect(txJSON.inputs.length).to.be.equal(1) - const p2wkhInput = txJSON.inputs[0] - expect(p2wkhInput.prevout.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() - ) - expect(p2wkhInput.prevout.index).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo - .outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wkhInput.witness.length).to.be.greaterThan(0) - expect(p2wkhInput.script.length).to.be.equal(0) - // The input comes from the main UTXO so the input should be the - // wallet's address - expect(p2wkhInput.address).to.be.equal(testnetWalletAddress) - - const p2shInput = txJSON.inputs[1] + const p2shInput = txJSON.inputs[0] expect(p2shInput.prevout.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo.transactionHash.toString() ) expect(p2shInput.prevout.index).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` @@ -567,46 +901,30 @@ describe("Sweep", () => { // Input's address should be set to the address generated from deposit // script hash expect(p2shInput.address).to.be.equal( - testnetDepositScripthashAddress - ) - - const p2wshInput = txJSON.inputs[2] - expect(p2wshInput.prevout.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() - ) - expect(p2wshInput.prevout.index).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo - .outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // witness script hash - expect(p2wshInput.address).to.be.equal( - testnetDepositWitnessScripthashAddress + "2N8iF1pRndihBzgLDna9MfRhmqktwTdHejA" ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) const sweepOutput = txJSON.outputs[0] - // Should be OP_0 . Public key corresponds to the - // wallet BTC address. + // OP_DUP OP_HASH160 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + // OP_EQUALVERIFY OP_CHECKSIG expect(sweepOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" + "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac" ) // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + expect(sweepOutput.address).to.be.equal( + "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" + ) // The output's value should be equal to the sum of all input values - // minus fee (17000 + 10000 + 35400 - 1600) - expect(sweepOutput.value).to.be.equal(60800) + // minus fee (15000- 1600) + expect(sweepOutput.value).to.be.equal(13400) }) it("should return the proper transaction hash", async () => { expect(transactionHash).to.be.deep.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep .transactionHash ) }) @@ -614,467 +932,238 @@ describe("Sweep", () => { it("should return the proper new main UTXO", () => { const expectedNewMainUtxo = { transactionHash: - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep .transactionHash, outputIndex: 0, - value: BigNumber.from(60800), + value: BigNumber.from(13400), } - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) }) }) - context( - "when main UTXO from previous deposit sweep is non-witness", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx + context("when there are no UTXOs", () => { + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - const utxosWithRaw = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } + await expect( + walletTx.depositSweep.assembleTransaction( + fee, + testnetWalletPrivateKey, + [], + [] ) + ).to.be.rejectedWith( + "There must be at least one deposit UTXO to sweep" + ) + }) + }) - const deposit = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data + context( + "when the numbers of UTXOs and deposit elements are not equal", + () => { + const utxosWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (data) => { + return data.utxo } ) - // P2WPKH - const mainUtxoWithRaw = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + // Add only one element to the deposit + const deposit = [ + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data, + ] - const witness = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness - - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - witness, - utxosWithRaw, - deposit, - mainUtxoWithRaw - )) - }) + const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep - .transaction + it("should revert", async () => { + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness ) - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - - expect(txJSON.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + await expect( + walletTx.depositSweep.assembleTransaction( + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit + ) + ).to.be.rejectedWith( + "Number of UTXOs must equal the number of deposit elements" ) - expect(txJSON.version).to.be.equal(1) + }) + } + ) - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(2) + context("when the main UTXO does not belong to the wallet", () => { + const utxoWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data + + // The UTXO below does not belong to the wallet + const mainUtxoWithRaw = { + transactionHash: BitcoinTxHash.from( + "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883" + ), + outputIndex: 1, + value: BigNumber.from(3933200), + transactionHex: + "0100000000010162cae24e74ad64f9f0493b09f3964908b3b3038f4924882d3d" + + "bd853b4c9bc7390100000000ffffffff02102700000000000017a914867120d5" + + "480a9cc0c11c1193fa59b3a92e852da78710043c00000000001600147ac2d937" + + "8a1c47e589dfb8095ca95ed2140d272602483045022100b70bd9b7f5d230444a" + + "542c7971bea79786b4ebde6703cee7b6ee8cd16e115ebf02204d50ea9d1ee08d" + + "e9741498c2cc64266e40d52c4adb9ef68e65aa2727cd4208b5012102ee067a02" + + "73f2e3ba88d23140a24fdb290f27bbcd0f94117a9c65be3911c5c04e00000000", + } - const p2wshInput = txJSON.inputs[0] - expect(p2wshInput.prevout.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2wshInput.prevout.index).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0] - .utxo.outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2wshInput.address).to.be.equal( - "tb1qk8urugnf08wfle6wslmdxq7mkz9z0gw8e6gkvspn7dx87tfpfntshdm7qr" - ) + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - const p2pkhInput = txJSON.inputs[1] // main UTXO - expect(p2pkhInput.prevout.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() - ) - expect(p2pkhInput.prevout.index).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - .outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2pkhInput.witness).to.be.equal("00") - expect(p2pkhInput.script.length).to.be.greaterThan(0) - // The input comes from the main UTXO so the input should be the - // wallet's address - expect(p2pkhInput.address).to.be.equal( - "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" + await expect( + walletTx.depositSweep.assembleTransaction( + fee, + testnetWalletPrivateKey, + [utxoWithRaw], + [deposit], + mainUtxoWithRaw ) + ).to.be.rejectedWith("UTXO does not belong to the wallet") + }) + }) - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) + context( + "when the wallet private does not correspond to the wallet public key", + () => { + const utxoWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data + const anotherPrivateKey = + "cRJvyxtoggjAm9A94cB86hZ7Y62z2ei5VNJHLksFi2xdnz1GJ6xt" - const sweepOutput = txJSON.outputs[0] - // Should be OP_0 . Public key corresponds to the - // wallet BTC address. - expect(sweepOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal(testnetWalletAddress) - // The output's value should be equal to the sum of all input values - // minus fee (16400 + 19000 - 1600) - expect(sweepOutput.value).to.be.equal(33800) - }) + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep - .transactionHash + await expect( + walletTx.depositSweep.assembleTransaction( + fee, + anotherPrivateKey, + [utxoWithRaw], + [deposit] + ) + ).to.be.rejectedWith( + "Wallet public key does not correspond to wallet private key" ) }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash, - outputIndex: 0, - value: BigNumber.from(33800), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) } ) - }) - }) - context("when the new main UTXO is requested to be non-witness", () => { - // The only difference between deposit sweep transactions with witness and - // non-witness output is the output type itself. - // Therefore only one test case was added for non-witness transactions. - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx - - const utxosWithRaw = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } - ) - - const deposit = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (deposit) => { - return deposit.data + context("when the type of UTXO is unsupported", () => { + // Use coinbase transaction of some block + const utxoWithRaw = { + transactionHash: BitcoinTxHash.from( + "025de155e6f2ffbbf4851493e0d28dad54020db221a3f38bf63c1f65e3d3595b" + ), + outputIndex: 0, + value: BigNumber.from(5000000000), + transactionHex: + "010000000100000000000000000000000000000000000000000000000000000000" + + "00000000ffffffff0e04db07c34f0103062f503253482fffffffff0100f2052a01" + + "000000232102db6a0f2ef2e970eb1d2a84eabb5337f9cac0d85b49f209bffc4ec6" + + "805802e6a5ac00000000", } - ) + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - const witness = depositSweepWithNoMainUtxoAndNonWitnessOutput.witness + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - witness, - utxosWithRaw, - deposit - )) + await expect( + walletTx.depositSweep.assembleTransaction( + fee, + testnetWalletPrivateKey, + [utxoWithRaw], + [deposit] + ) + ).to.be.rejectedWith("Unsupported UTXO script type") + }) + }) }) + }) + }) - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + describe("Spv", () => { + describe("submitDepositSweepProof", () => { + let bitcoinClient: MockBitcoinClient + let tbtcContracts: MockTBTCContracts + let maintenanceService: MaintenanceService - expect(txJSON.hash).to.be.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) + beforeEach(async () => { + bcoin.set("testnet") - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() - const p2shInput = txJSON.inputs[0] - expect(p2shInput.prevout.hash).to.be.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2shInput.prevout.index).to.be.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo - .outputIndex + maintenanceService = new MaintenanceService( + tbtcContracts, + bitcoinClient ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.equal("00") - expect(p2shInput.script.length).to.be.greaterThan(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2shInput.address).to.be.equal( - "2N8iF1pRndihBzgLDna9MfRhmqktwTdHejA" + + const transactionHash = + depositSweepProof.bitcoinChainData.transaction.transactionHash + const transactions = new Map() + transactions.set( + transactionHash.toString(), + depositSweepProof.bitcoinChainData.transaction ) + bitcoinClient.transactions = transactions - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) + const rawTransactions = new Map() + rawTransactions.set( + transactionHash.toString(), + depositSweepProof.bitcoinChainData.rawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions - const sweepOutput = txJSON.outputs[0] - // OP_DUP OP_HASH160 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - // OP_EQUALVERIFY OP_CHECKSIG - expect(sweepOutput.script).to.be.equal( - "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac" + bitcoinClient.latestHeight = + depositSweepProof.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = + depositSweepProof.bitcoinChainData.headersChain + bitcoinClient.transactionMerkle = + depositSweepProof.bitcoinChainData.transactionMerkleBranch + const confirmations = new Map() + confirmations.set( + transactionHash.toString(), + depositSweepProof.bitcoinChainData.accumulatedTxConfirmations ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal( - "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" + bitcoinClient.confirmations = confirmations + await maintenanceService.spv.submitDepositSweepProof( + transactionHash, + NO_MAIN_UTXO ) - // The output's value should be equal to the sum of all input values - // minus fee (15000- 1600) - expect(sweepOutput.value).to.be.equal(13400) }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash + it("should submit deposit sweep proof with correct arguments", () => { + const bridgeLog = tbtcContracts.bridge.depositSweepProofLog + expect(bridgeLog.length).to.equal(1) + expect(bridgeLog[0].mainUtxo).to.equal(NO_MAIN_UTXO) + expect(bridgeLog[0].sweepTx).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepTx + ) + expect(bridgeLog[0].sweepProof.txIndexInBlock).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.txIndexInBlock + ) + expect(bridgeLog[0].sweepProof.merkleProof).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.merkleProof + ) + expect(bridgeLog[0].sweepProof.bitcoinHeaders).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.bitcoinHeaders ) }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(13400), - } - - expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) - }) - }) - - context("when there are no UTXOs", () => { - it("should revert", async () => { - await expect( - assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - true, - [], - [] - ) - ).to.be.rejectedWith("There must be at least one deposit UTXO to sweep") - }) - }) - - context( - "when the numbers of UTXOs and deposit elements are not equal", - () => { - const utxosWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map((data) => { - return data.utxo - }) - - // Add only one element to the deposit - const deposit = [ - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data, - ] - - const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - - it("should revert", async () => { - await expect( - assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - witness, - utxosWithRaw, - deposit - ) - ).to.be.rejectedWith( - "Number of UTXOs must equal the number of deposit elements" - ) - }) - } - ) - - context("when the main UTXO does not belong to the wallet", () => { - const utxoWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - - // The UTXO below does not belong to the wallet - const mainUtxoWithRaw = { - transactionHash: BitcoinTxHash.from( - "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883" - ), - outputIndex: 1, - value: BigNumber.from(3933200), - transactionHex: - "0100000000010162cae24e74ad64f9f0493b09f3964908b3b3038f4924882d3d" + - "bd853b4c9bc7390100000000ffffffff02102700000000000017a914867120d5" + - "480a9cc0c11c1193fa59b3a92e852da78710043c00000000001600147ac2d937" + - "8a1c47e589dfb8095ca95ed2140d272602483045022100b70bd9b7f5d230444a" + - "542c7971bea79786b4ebde6703cee7b6ee8cd16e115ebf02204d50ea9d1ee08d" + - "e9741498c2cc64266e40d52c4adb9ef68e65aa2727cd4208b5012102ee067a02" + - "73f2e3ba88d23140a24fdb290f27bbcd0f94117a9c65be3911c5c04e00000000", - } - - it("should revert", async () => { - await expect( - assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - true, - [utxoWithRaw], - [deposit], - mainUtxoWithRaw - ) - ).to.be.rejectedWith("UTXO does not belong to the wallet") - }) - }) - - context( - "when the wallet private does not correspond to the wallet public key", - () => { - const utxoWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - const anotherPrivateKey = - "cRJvyxtoggjAm9A94cB86hZ7Y62z2ei5VNJHLksFi2xdnz1GJ6xt" - - it("should revert", async () => { - await expect( - assembleDepositSweepTransaction( - fee, - anotherPrivateKey, - true, - [utxoWithRaw], - [deposit] - ) - ).to.be.rejectedWith( - "Wallet public key does not correspond to wallet private key" - ) - }) - } - ) - - context("when the type of UTXO is unsupported", () => { - // Use coinbase transaction of some block - const utxoWithRaw = { - transactionHash: BitcoinTxHash.from( - "025de155e6f2ffbbf4851493e0d28dad54020db221a3f38bf63c1f65e3d3595b" - ), - outputIndex: 0, - value: BigNumber.from(5000000000), - transactionHex: - "010000000100000000000000000000000000000000000000000000000000000000" + - "00000000ffffffff0e04db07c34f0103062f503253482fffffffff0100f2052a01" + - "000000232102db6a0f2ef2e970eb1d2a84eabb5337f9cac0d85b49f209bffc4ec6" + - "805802e6a5ac00000000", - } - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - - it("should revert", async () => { - await expect( - assembleDepositSweepTransaction( - fee, - testnetWalletPrivateKey, - true, - [utxoWithRaw], - [deposit] - ) - ).to.be.rejectedWith("Unsupported UTXO script type") - }) - }) - }) - - describe("submitDepositSweepProof", () => { - let bitcoinClient: MockBitcoinClient - let bridge: MockBridge - - beforeEach(async () => { - bcoin.set("testnet") - - bitcoinClient = new MockBitcoinClient() - bridge = new MockBridge() - - const transactionHash = - depositSweepProof.bitcoinChainData.transaction.transactionHash - const transactions = new Map() - transactions.set( - transactionHash.toString(), - depositSweepProof.bitcoinChainData.transaction - ) - bitcoinClient.transactions = transactions - - const rawTransactions = new Map() - rawTransactions.set( - transactionHash.toString(), - depositSweepProof.bitcoinChainData.rawTransaction - ) - bitcoinClient.rawTransactions = rawTransactions - - bitcoinClient.latestHeight = - depositSweepProof.bitcoinChainData.latestBlockHeight - bitcoinClient.headersChain = - depositSweepProof.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = - depositSweepProof.bitcoinChainData.transactionMerkleBranch - const confirmations = new Map() - confirmations.set( - transactionHash.toString(), - depositSweepProof.bitcoinChainData.accumulatedTxConfirmations - ) - bitcoinClient.confirmations = confirmations - await submitDepositSweepProof( - transactionHash, - NO_MAIN_UTXO, - bridge, - bitcoinClient - ) - }) - - it("should submit deposit sweep proof with correct arguments", () => { - const bridgeLog = bridge.depositSweepProofLog - expect(bridgeLog.length).to.equal(1) - expect(bridgeLog[0].mainUtxo).to.equal(NO_MAIN_UTXO) - expect(bridgeLog[0].sweepTx).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepTx - ) - expect(bridgeLog[0].sweepProof.txIndexInBlock).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepProof.txIndexInBlock - ) - expect(bridgeLog[0].sweepProof.merkleProof).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepProof.merkleProof - ) - expect(bridgeLog[0].sweepProof.bitcoinHeaders).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepProof.bitcoinHeaders - ) }) }) }) diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index 3b179c4c5..45887102d 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -13,17 +13,15 @@ import { BitcoinTxHash, BitcoinUtxo, extractBitcoinRawTxVectors, -} from "../src/lib/bitcoin" -import { DepositReceipt } from "../src/lib/contracts" -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import bcoin from "bcoin" -import { EthereumAddress } from "../src/lib/ethereum" -import { BitcoinNetwork } from "../src" -import { + DepositReceipt, + EthereumAddress, + BitcoinNetwork, DepositFunding, DepositScript, Deposit, -} from "../src/services/deposits" +} from "../src" +import { MockBitcoinClient } from "./utils/mock-bitcoin-client" +import bcoin from "bcoin" import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" describe("Deposit", () => { diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index 0775f4b5e..6d8d80020 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -1,9 +1,9 @@ import { + BitcoinNetwork, ElectrumCredentials, ElectrumClient, computeElectrumScriptHash, -} from "../src/lib/electrum" -import { BitcoinNetwork } from "../src/lib/bitcoin" +} from "../src" import { testnetAddress, testnetHeadersChain, diff --git a/typescript/test/ethereum.test.ts b/typescript/test/ethereum.test.ts index 1a9c9f1ba..45e248a0f 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/ethereum.test.ts @@ -1,8 +1,11 @@ import { + BitcoinTxHash, + BitcoinHashUtils, EthereumAddress, EthereumBridge, EthereumTBTCToken, -} from "../src/lib/ethereum" + Hex, +} from "../src" import { deployMockContract, MockContract, @@ -14,8 +17,6 @@ import { abi as TBTCTokenABI } from "@keep-network/tbtc-v2/artifacts/TBTC.json" import { abi as WalletRegistryABI } from "@keep-network/ecdsa/artifacts/WalletRegistry.json" import { MockProvider } from "@ethereum-waffle/provider" import { waffleChai } from "@ethereum-waffle/chai" -import { BitcoinTxHash, BitcoinHashUtils } from "../src/lib/bitcoin" -import { Hex } from "../src/lib/utils" chai.use(waffleChai) diff --git a/typescript/test/hex.test.ts b/typescript/test/hex.test.ts index 1f8e538e3..252f1a93d 100644 --- a/typescript/test/hex.test.ts +++ b/typescript/test/hex.test.ts @@ -1,5 +1,5 @@ import { assert } from "chai" -import { Hex } from "../src/lib/utils" +import { Hex } from "../src" describe("Hex", () => { const stringUnprefixed = diff --git a/typescript/test/proof.test.ts b/typescript/test/proof.test.ts index 9f499ac1e..fc52d7bc2 100644 --- a/typescript/test/proof.test.ts +++ b/typescript/test/proof.test.ts @@ -6,8 +6,8 @@ import { BitcoinSpvProof, assembleBitcoinSpvProof, validateBitcoinSpvProof, -} from "../src/lib/bitcoin" -import { Hex } from "../src/lib/utils" + Hex, +} from "../src" import { singleInputProofTestData, multipleInputsProofTestData, diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index f9a3a5c68..468360684 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -6,7 +6,14 @@ import { BitcoinTx, BitcoinTxHash, BitcoinUtxo, -} from "../src/lib/bitcoin" + NewWalletRegisteredEvent, + RedemptionRequest, + Wallet, + WalletState, + RedemptionsService, + WalletTx, + MaintenanceService, +} from "../src" import bcoin from "bcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { @@ -25,23 +32,11 @@ import { walletPrivateKey, walletPublicKey, } from "./data/redemption" -import { - NewWalletRegisteredEvent, - RedemptionRequest, - Wallet, - WalletState, -} from "../src/lib/contracts" -import { - assembleRedemptionTransaction, - submitRedemptionProof, - submitRedemptionTransaction, -} from "../src/redemption" import { MockBridge } from "./utils/mock-bridge" import * as chai from "chai" import { expect } from "chai" import chaiAsPromised from "chai-as-promised" import { BigNumber, BigNumberish } from "ethers" -import { RedemptionsService } from "../src/services/redemptions" import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" import { Hex } from "../src" @@ -816,28 +811,218 @@ describe("Redemption", () => { }) }) - describe("submitRedemptionTransaction", () => { - let bitcoinClient: MockBitcoinClient - let bridge: MockBridge + describe("WalletTx", () => { + describe("Redemption", () => { + describe("submitTransaction", () => { + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient - beforeEach(async () => { - bcoin.set("testnet") - bitcoinClient = new MockBitcoinClient() - bridge = new MockBridge() - }) + beforeEach(async () => { + bcoin.set("testnet") - context("when there are redemption requests provided", () => { - context( - "when all redeemer output scripts identify pending redemptions", - () => { - context("when there is a change created", () => { - context("when the change output is P2WPKH", () => { - context("when there is a single redeemer", () => { - context( - "when the redeemer output script is derived from a P2PKH address", - () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + }) + + context("when there are redemption requests provided", () => { + context( + "when all redeemer output scripts identify pending redemptions", + () => { + context("when there is a change created", () => { + context("when the change output is P2WPKH", () => { + context("when there is a single redeemer", () => { + context( + "when the redeemer output script is derived from a P2PKH address", + () => { + const data: RedemptionTestData = + singleP2PKHRedemptionWithWitnessChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + + beforeEach(async () => { + ;({ transactionHash, newMainUtxo } = + await runRedemptionScenario( + walletPrivateKey, + bitcoinClient, + tbtcContracts, + data + )) + }) + + it("should broadcast redemption transaction with proper structure", () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal( + 1 + ) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + data.expectedRedemption.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1472680), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context( + "when the redeemer output script is derived from a P2WPKH address", + () => { + const data: RedemptionTestData = + singleP2WPKHRedemptionWithWitnessChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + + beforeEach(async () => { + ;({ transactionHash, newMainUtxo } = + await runRedemptionScenario( + walletPrivateKey, + bitcoinClient, + tbtcContracts, + data + )) + }) + + it("should broadcast redemption transaction with proper structure", () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal( + 1 + ) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + data.expectedRedemption.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1458780), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context( + "when the redeemer output script is derived from a P2SH address", + () => { + const data: RedemptionTestData = + singleP2SHRedemptionWithWitnessChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + + beforeEach(async () => { + ;({ transactionHash, newMainUtxo } = + await runRedemptionScenario( + walletPrivateKey, + bitcoinClient, + tbtcContracts, + data + )) + }) + + it("should broadcast redemption transaction with proper structure", () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal( + 1 + ) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + data.expectedRedemption.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1446580), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context( + "when the redeemer output script is derived from a P2WSH address", + () => { + const data: RedemptionTestData = + singleP2WSHRedemptionWithWitnessChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + + beforeEach(async () => { + ;({ transactionHash, newMainUtxo } = + await runRedemptionScenario( + walletPrivateKey, + bitcoinClient, + tbtcContracts, + data + )) + }) + + it("should broadcast redemption transaction with proper structure", () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal( + 1 + ) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + data.expectedRedemption.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1429580), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + }) + + context("when there are multiple redeemers", () => { const data: RedemptionTestData = - singleP2PKHRedemptionWithWitnessChange + multipleRedemptionsWithWitnessChange let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo | undefined @@ -847,7 +1032,7 @@ describe("Redemption", () => { await runRedemptionScenario( walletPrivateKey, bitcoinClient, - bridge, + tbtcContracts, data )) }) @@ -869,39 +1054,301 @@ describe("Redemption", () => { const expectedNewMainUtxo = { transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1472680), + outputIndex: 4, + value: BigNumber.from(1375180), } expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) }) - } + }) + }) + + context("when the change output is P2PKH", () => { + // The only difference between redemption transactions with P2PKH and + // P2WPKH change is the output type. + // Therefore only one test case was added for P2PKH transactions. + const data: RedemptionTestData = + singleP2SHRedemptionWithNonWitnessChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + + beforeEach(async () => { + ;({ transactionHash, newMainUtxo } = + await runRedemptionScenario( + walletPrivateKey, + bitcoinClient, + tbtcContracts, + data + )) + }) + + it("should broadcast redemption transaction with proper structure", () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + data.expectedRedemption.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1364180), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + }) + }) + + context("when there is no change UTXO created", () => { + // Use test data with the treasury fees of all the redemption requests + // set to 0. This is the only situation that the redemption transaction + // will not contain the change output. + const data: RedemptionTestData = + multipleRedemptionsWithoutChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + + beforeEach(async () => { + ;({ transactionHash, newMainUtxo } = + await runRedemptionScenario( + walletPrivateKey, + bitcoinClient, + tbtcContracts, + data + )) + }) + + it("should broadcast redemption transaction with proper structure", () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + data.expectedRedemption.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should not return the new main UTXO", () => { + expect(newMainUtxo).to.be.undefined + }) + }) + } + ) + + context( + "when not all redeemer output scripts identify pending redemptions", + () => { + const data: RedemptionTestData = + multipleRedemptionsWithWitnessChange + + beforeEach(async () => { + const rawTransactions = new Map() + rawTransactions.set(data.mainUtxo.transactionHash.toString(), { + transactionHex: data.mainUtxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + + const pendingRedemptions = new Map< + BigNumberish, + RedemptionRequest + >( + data.pendingRedemptions.map((redemption) => [ + redemption.redemptionKey, + redemption.pendingRedemption, + ]) + ) + + // Before setting the pending redemption map in the Bridge, delete + // one element to simulate absence of that redemption + pendingRedemptions.delete( + data.pendingRedemptions[2].redemptionKey ) + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + }) + + it("should revert", async () => { + const redeemerOutputScripts = data.pendingRedemptions.map( + (redemption) => + redemption.pendingRedemption.redeemerOutputScript + ) + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + + await expect( + walletTx.redemption.submitTransaction( + walletPrivateKey, + data.mainUtxo, + redeemerOutputScripts + ) + ).to.be.rejectedWith("Redemption request does not exist") + }) + } + ) + }) + + context("when there are no redemption requests provided", () => { + const data: RedemptionTestData = + singleP2WPKHRedemptionWithWitnessChange + + beforeEach(async () => { + const rawTransactions = new Map() + rawTransactions.set(data.mainUtxo.transactionHash.toString(), { + transactionHex: data.mainUtxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + }) + + it("should revert", async () => { + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + + await expect( + walletTx.redemption.submitTransaction( + walletPrivateKey, + data.mainUtxo, + [] // empty redeemer output script list + ) + ).to.be.rejectedWith("There must be at least one request to redeem") + }) + }) + }) + + describe("assembleTransaction", () => { + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + }) + context("when there are redemption requests provided", () => { + context("when there is a change UTXO created", () => { + describe("when the change output is P2WPKH", () => { + context("when there is a single redeemer", () => { context( - "when the redeemer output script is derived from a P2WPKH address", + "when the redeemer output script is derived from a P2PKH address", () => { const data: RedemptionTestData = - singleP2WPKHRedemptionWithWitnessChange + singleP2PKHRedemptionWithWitnessChange let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { - ;({ transactionHash, newMainUtxo } = - await runRedemptionScenario( - walletPrivateKey, - bitcoinClient, - bridge, - data - )) + const redemptionRequests = data.pendingRedemptions.map( + (redemption) => redemption.pendingRedemption + ) + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.redemption.assembleTransaction( + walletPrivateKey, + data.mainUtxo, + redemptionRequests + )) }) - it("should broadcast redemption transaction with proper structure", () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( + it("should return transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql( data.expectedRedemption.transaction ) + + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from( + transaction.transactionHex, + "hex" + ) + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + + expect(txJSON.hash).to.be.equal( + data.expectedRedemption.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) + + const input = txJSON.inputs[0] + + expect(input.prevout.hash).to.be.equal( + data.mainUtxo.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const p2pkhOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // P2PKH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 10000 - 1600 - 1000 = 7400 + expect(p2pkhOutput.value).to.be.equal(7400) + // The output script should correspond to: + // OP_DUP OP_HASH160 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 + // OP_EQUALVERIFY OP_CHECKSIG + expect(p2pkhOutput.script).to.be.equal( + "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac" + ) + // The output address should be P2PKH + expect(p2pkhOutput.address).to.be.equal( + "mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5" + ) + + // P2WPKH output (change) + // The value of fee should be the fee share of the (only) redeem output + // which is 1600 + // The output value should be main UTXO input value - fee - the + // value of the output, which is 1472680 = 1481680 - 1600 - 7400 + expect(changeOutput.value).to.be.equal(1472680) + // The output script should correspond to: + // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + expect(changeOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The change output address should be the P2WPKH address of the wallet + expect(changeOutput.address).to.be.equal( + p2wpkhWalletAddress + ) }) it("should return the proper transaction hash", async () => { @@ -915,7 +1362,7 @@ describe("Redemption", () => { transactionHash: data.expectedRedemption.transactionHash, outputIndex: 1, - value: BigNumber.from(1458780), + value: BigNumber.from(1472680), } expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) @@ -924,29 +1371,107 @@ describe("Redemption", () => { ) context( - "when the redeemer output script is derived from a P2SH address", + "when the redeemer output script is derived from a P2WPKH address", () => { const data: RedemptionTestData = - singleP2SHRedemptionWithWitnessChange + singleP2WPKHRedemptionWithWitnessChange let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { - ;({ transactionHash, newMainUtxo } = - await runRedemptionScenario( - walletPrivateKey, - bitcoinClient, - bridge, - data - )) + const redemptionRequests = data.pendingRedemptions.map( + (redemption) => redemption.pendingRedemption + ) + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.redemption.assembleTransaction( + walletPrivateKey, + data.mainUtxo, + redemptionRequests + )) }) - it("should broadcast redemption transaction with proper structure", () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( + it("should return transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql( data.expectedRedemption.transaction ) + + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from( + transaction.transactionHex, + "hex" + ) + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + + expect(txJSON.hash).to.be.equal( + data.expectedRedemption.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) + + const input = txJSON.inputs[0] + + expect(input.prevout.hash).to.be.equal( + data.mainUtxo.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const p2wpkhOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // P2WPKH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 15000 - 1700 - 1100 = 12200 + expect(p2wpkhOutput.value).to.be.equal(12200) + // The output script should correspond to: + // OP_0 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 + expect(p2wpkhOutput.script).to.be.equal( + "00144130879211c54df460e484ddf9aac009cb38ee74" + ) + // The output address should be P2WPKH + expect(p2wpkhOutput.address).to.be.equal( + "tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l" + ) + + // P2WPKH output (change) + // The value of fee should be the fee share of the (only) redeem output + // which is 1700 + // The output value should be main UTXO input value - fee - the + // value of the output, which is 1458780 = 1472680 - 1700 - 12200 + expect(changeOutput.value).to.be.equal(1458780) + // The output script should correspond to: + // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + expect(changeOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The change output address should be the P2WPKH address of the wallet + expect(changeOutput.address).to.be.equal( + p2wpkhWalletAddress + ) }) it("should return the proper transaction hash", async () => { @@ -960,7 +1485,7 @@ describe("Redemption", () => { transactionHash: data.expectedRedemption.transactionHash, outputIndex: 1, - value: BigNumber.from(1446580), + value: BigNumber.from(1458780), } expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) @@ -969,29 +1494,107 @@ describe("Redemption", () => { ) context( - "when the redeemer output script is derived from a P2WSH address", + "when the redeemer output script is derived from a P2SH address", () => { const data: RedemptionTestData = - singleP2WSHRedemptionWithWitnessChange + singleP2SHRedemptionWithWitnessChange let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx beforeEach(async () => { - ;({ transactionHash, newMainUtxo } = - await runRedemptionScenario( - walletPrivateKey, - bitcoinClient, - bridge, - data - )) + const redemptionRequests = data.pendingRedemptions.map( + (redemption) => redemption.pendingRedemption + ) + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.redemption.assembleTransaction( + walletPrivateKey, + data.mainUtxo, + redemptionRequests + )) }) - it("should broadcast redemption transaction with proper structure", () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( + it("should return transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql( data.expectedRedemption.transaction ) + + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from( + transaction.transactionHex, + "hex" + ) + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + + expect(txJSON.hash).to.be.equal( + data.expectedRedemption.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) + + const input = txJSON.inputs[0] + + expect(input.prevout.hash).to.be.equal( + data.mainUtxo.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const p2shOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // P2SH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 13000 - 1700 - 800 = 10500 + expect(p2shOutput.value).to.be.equal(10500) + // The output script should correspond to: + // OP_HASH160 0x14 0x3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL + expect(p2shOutput.script).to.be.equal( + "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" + ) + // The output address should be P2SH + expect(p2shOutput.address).to.be.equal( + "2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C" + ) + + // P2WPKH output (change) + // The value of fee should be the fee share of the (only) redeem output + // which is 1700 + // The output value should be main UTXO input value - fee - the + // value of the output, which is 1446580 = 1458780 - 1700 - 10500 + expect(changeOutput.value).to.be.equal(1446580) + // The output script should correspond to: + // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + expect(changeOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The change output address should be the P2WPKH address of the wallet + expect(changeOutput.address).to.be.equal( + p2wpkhWalletAddress + ) }) it("should return the proper transaction hash", async () => { @@ -1005,558 +1608,141 @@ describe("Redemption", () => { transactionHash: data.expectedRedemption.transactionHash, outputIndex: 1, - value: BigNumber.from(1429580), + value: BigNumber.from(1446580), } expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) }) } ) - }) - context("when there are multiple redeemers", () => { - const data: RedemptionTestData = - multipleRedemptionsWithWitnessChange + context( + "when the redeemer output script is derived from a P2WSH address", + () => { + const data: RedemptionTestData = + singleP2WSHRedemptionWithWitnessChange - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx - beforeEach(async () => { - ;({ transactionHash, newMainUtxo } = - await runRedemptionScenario( - walletPrivateKey, - bitcoinClient, - bridge, - data - )) - }) + beforeEach(async () => { + const redemptionRequests = data.pendingRedemptions.map( + (redemption) => redemption.pendingRedemption + ) - it("should broadcast redemption transaction with proper structure", () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - data.expectedRedemption.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 4, - value: BigNumber.from(1375180), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - }) - }) - - context("when the change output is P2PKH", () => { - // The only difference between redemption transactions with P2PKH and - // P2WPKH change is the output type. - // Therefore only one test case was added for P2PKH transactions. - const data: RedemptionTestData = - singleP2SHRedemptionWithNonWitnessChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - - beforeEach(async () => { - ;({ transactionHash, newMainUtxo } = - await runRedemptionScenario( - walletPrivateKey, - bitcoinClient, - bridge, - data - )) - }) - - it("should broadcast redemption transaction with proper structure", () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - data.expectedRedemption.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1364180), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - }) - }) - - context("when there is no change UTXO created", () => { - // Use test data with the treasury fees of all the redemption requests - // set to 0. This is the only situation that the redemption transaction - // will not contain the change output. - const data: RedemptionTestData = multipleRedemptionsWithoutChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - - beforeEach(async () => { - ;({ transactionHash, newMainUtxo } = await runRedemptionScenario( - walletPrivateKey, - bitcoinClient, - bridge, - data - )) - }) - - it("should broadcast redemption transaction with proper structure", () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - data.expectedRedemption.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) - - it("should not return the new main UTXO", () => { - expect(newMainUtxo).to.be.undefined - }) - }) - } - ) - - context( - "when not all redeemer output scripts identify pending redemptions", - () => { - const data: RedemptionTestData = multipleRedemptionsWithWitnessChange - - beforeEach(async () => { - const rawTransactions = new Map() - rawTransactions.set(data.mainUtxo.transactionHash.toString(), { - transactionHex: data.mainUtxo.transactionHex, - }) - bitcoinClient.rawTransactions = rawTransactions - - const pendingRedemptions = new Map( - data.pendingRedemptions.map((redemption) => [ - redemption.redemptionKey, - redemption.pendingRedemption, - ]) - ) - - // Before setting the pending redemption map in the Bridge, delete - // one element to simulate absence of that redemption - pendingRedemptions.delete(data.pendingRedemptions[2].redemptionKey) - bridge.setPendingRedemptions(pendingRedemptions) - }) - - it("should revert", async () => { - const redeemerOutputScripts = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption.redeemerOutputScript - ) - - await expect( - submitRedemptionTransaction( - bitcoinClient, - bridge, - walletPrivateKey, - data.mainUtxo, - redeemerOutputScripts, - data.witness - ) - ).to.be.rejectedWith("Redemption request does not exist") - }) - } - ) - }) - - context("when there are no redemption requests provided", () => { - const data: RedemptionTestData = singleP2WPKHRedemptionWithWitnessChange - - beforeEach(async () => { - const rawTransactions = new Map() - rawTransactions.set(data.mainUtxo.transactionHash.toString(), { - transactionHex: data.mainUtxo.transactionHex, - }) - bitcoinClient.rawTransactions = rawTransactions - }) - - it("should revert", async () => { - await expect( - submitRedemptionTransaction( - bitcoinClient, - bridge, - walletPrivateKey, - data.mainUtxo, - [], // empty redeemer output script list - data.witness - ) - ).to.be.rejectedWith("There must be at least one request to redeem") - }) - }) - }) - - describe("assembleRedemptionTransaction", () => { - context("when there are redemption requests provided", () => { - context("when there is a change UTXO created", () => { - describe("when the change output is P2WPKH", () => { - context("when there is a single redeemer", () => { - context( - "when the redeemer output script is derived from a P2PKH address", - () => { - const data: RedemptionTestData = - singleP2PKHRedemptionWithWitnessChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - let transaction: BitcoinRawTx - - beforeEach(async () => { - const redemptionRequests = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption - ) - - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleRedemptionTransaction( - walletPrivateKey, - data.mainUtxo, - redemptionRequests, - data.witness - )) - }) - - it("should return transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql( - data.expectedRedemption.transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - - expect(txJSON.hash).to.be.equal( - data.expectedRedemption.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) - - const input = txJSON.inputs[0] - - expect(input.prevout.hash).to.be.equal( - data.mainUtxo.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) - - const p2pkhOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] - - // P2PKH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 10000 - 1600 - 1000 = 7400 - expect(p2pkhOutput.value).to.be.equal(7400) - // The output script should correspond to: - // OP_DUP OP_HASH160 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 - // OP_EQUALVERIFY OP_CHECKSIG - expect(p2pkhOutput.script).to.be.equal( - "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac" - ) - // The output address should be P2PKH - expect(p2pkhOutput.address).to.be.equal( - "mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5" - ) - - // P2WPKH output (change) - // The value of fee should be the fee share of the (only) redeem output - // which is 1600 - // The output value should be main UTXO input value - fee - the - // value of the output, which is 1472680 = 1481680 - 1600 - 7400 - expect(changeOutput.value).to.be.equal(1472680) - // The output script should correspond to: - // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - expect(changeOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The change output address should be the P2WPKH address of the wallet - expect(changeOutput.address).to.be.equal(p2wpkhWalletAddress) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1472680), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context( - "when the redeemer output script is derived from a P2WPKH address", - () => { - const data: RedemptionTestData = - singleP2WPKHRedemptionWithWitnessChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - let transaction: BitcoinRawTx - - beforeEach(async () => { - const redemptionRequests = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption - ) - - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleRedemptionTransaction( - walletPrivateKey, - data.mainUtxo, - redemptionRequests, - data.witness - )) - }) - - it("should return transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql( - data.expectedRedemption.transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - - expect(txJSON.hash).to.be.equal( - data.expectedRedemption.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) - - const input = txJSON.inputs[0] - - expect(input.prevout.hash).to.be.equal( - data.mainUtxo.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) - - const p2wpkhOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] - - // P2WPKH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 15000 - 1700 - 1100 = 12200 - expect(p2wpkhOutput.value).to.be.equal(12200) - // The output script should correspond to: - // OP_0 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 - expect(p2wpkhOutput.script).to.be.equal( - "00144130879211c54df460e484ddf9aac009cb38ee74" - ) - // The output address should be P2WPKH - expect(p2wpkhOutput.address).to.be.equal( - "tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l" - ) - - // P2WPKH output (change) - // The value of fee should be the fee share of the (only) redeem output - // which is 1700 - // The output value should be main UTXO input value - fee - the - // value of the output, which is 1458780 = 1472680 - 1700 - 12200 - expect(changeOutput.value).to.be.equal(1458780) - // The output script should correspond to: - // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - expect(changeOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The change output address should be the P2WPKH address of the wallet - expect(changeOutput.address).to.be.equal(p2wpkhWalletAddress) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1458780), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context( - "when the redeemer output script is derived from a P2SH address", - () => { - const data: RedemptionTestData = - singleP2SHRedemptionWithWitnessChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - let transaction: BitcoinRawTx - - beforeEach(async () => { - const redemptionRequests = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption - ) - - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleRedemptionTransaction( - walletPrivateKey, - data.mainUtxo, - redemptionRequests, - data.witness - )) - }) - - it("should return transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql( - data.expectedRedemption.transaction - ) + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.redemption.assembleTransaction( + walletPrivateKey, + data.mainUtxo, + redemptionRequests + )) + }) - expect(txJSON.hash).to.be.equal( - data.expectedRedemption.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) + it("should return transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql( + data.expectedRedemption.transaction + ) - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from( + transaction.transactionHex, + "hex" + ) + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - const input = txJSON.inputs[0] + expect(txJSON.hash).to.be.equal( + data.expectedRedemption.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) - expect(input.prevout.hash).to.be.equal( - data.mainUtxo.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) + const input = txJSON.inputs[0] - const p2shOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] + expect(input.prevout.hash).to.be.equal( + data.mainUtxo.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const p2wshOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // P2WSH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 18000 - 1400 - 1000 = 15600 + expect(p2wshOutput.value).to.be.equal(15600) + // The output script should correspond to: + // OP_0 0x20 0x86a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96 + expect(p2wshOutput.script).to.be.equal( + "002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96" + ) + // The output address should be P2WSH + expect(p2wshOutput.address).to.be.equal( + "tb1qs63s8nwjut4tr5t8nudgzwp4m3dpkefjzpmumn90pruce0cye2tq2jkq0y" + ) - // P2SH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 13000 - 1700 - 800 = 10500 - expect(p2shOutput.value).to.be.equal(10500) - // The output script should correspond to: - // OP_HASH160 0x14 0x3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL - expect(p2shOutput.script).to.be.equal( - "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" - ) - // The output address should be P2SH - expect(p2shOutput.address).to.be.equal( - "2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C" - ) + // P2WPKH output (change) + // The value of fee should be the fee share of the (only) redeem output + // which is 1400 + // The output value should be main UTXO input value - fee - the + // value of the output, which is 1429580 = 1446580 - 1400 - 15600 + expect(changeOutput.value).to.be.equal(1429580) + // The output script should correspond to: + // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + expect(changeOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The change output address should be the P2WPKH address of the wallet + expect(changeOutput.address).to.be.equal( + p2wpkhWalletAddress + ) + }) - // P2WPKH output (change) - // The value of fee should be the fee share of the (only) redeem output - // which is 1700 - // The output value should be main UTXO input value - fee - the - // value of the output, which is 1446580 = 1458780 - 1700 - 10500 - expect(changeOutput.value).to.be.equal(1446580) - // The output script should correspond to: - // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - expect(changeOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The change output address should be the P2WPKH address of the wallet - expect(changeOutput.address).to.be.equal(p2wpkhWalletAddress) - }) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1429580), + } - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1446580), + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) } + ) + }) - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context( - "when the redeemer output script is derived from a P2WSH address", - () => { + context("when there are multiple redeemers", () => { const data: RedemptionTestData = - singleP2WSHRedemptionWithWitnessChange + multipleRedemptionsWithWitnessChange let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo | undefined @@ -1567,15 +1753,20 @@ describe("Redemption", () => { (redemption) => redemption.pendingRedemption ) + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + ;({ transactionHash, newMainUtxo, rawTransaction: transaction, - } = await assembleRedemptionTransaction( + } = await walletTx.redemption.assembleTransaction( walletPrivateKey, data.mainUtxo, - redemptionRequests, - data.witness + redemptionRequests )) }) @@ -1612,15 +1803,61 @@ describe("Redemption", () => { expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) + expect(txJSON.outputs.length).to.be.equal(5) + + const p2pkhOutput = txJSON.outputs[0] + const p2wpkhOutput = txJSON.outputs[1] + const p2shOutput = txJSON.outputs[2] + const p2wshOutput = txJSON.outputs[3] + const changeOutput = txJSON.outputs[4] + + // P2PKH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 18000 - 1100 - 1000 = 15900 + expect(p2pkhOutput.value).to.be.equal(15900) + // The output script should correspond to: + // OP_DUP OP_HASH160 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 + // OP_EQUALVERIFY OP_CHECKSIG + expect(p2pkhOutput.script).to.be.equal( + "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac" + ) + // The output address should be P2PKH + expect(p2pkhOutput.address).to.be.equal( + "mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5" + ) + + // P2WPKH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 13000 - 900 - 800 = 11300 + expect(p2wpkhOutput.value).to.be.equal(11300) + // The output script should correspond to: + // OP_0 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 + expect(p2wpkhOutput.script).to.be.equal( + "00144130879211c54df460e484ddf9aac009cb38ee74" + ) + // The output address should be P2WPKH + expect(p2wpkhOutput.address).to.be.equal( + "tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l" + ) - const p2wshOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] + // P2SH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 12000 - 1000 - 1100 = 9900 + expect(p2shOutput.value).to.be.equal(9900) + // The output script should correspond to: + // OP_HASH160 0x14 0x3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL + expect(p2shOutput.script).to.be.equal( + "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" + ) + // The output address should be P2SH + expect(p2shOutput.address).to.be.equal( + "2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C" + ) // P2WSH output // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 18000 - 1400 - 1000 = 15600 - expect(p2wshOutput.value).to.be.equal(15600) + // which is 15000 - 1400 - 700 = 12900 + expect(p2wshOutput.value).to.be.equal(12900) // The output script should correspond to: // OP_0 0x20 0x86a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96 expect(p2wshOutput.script).to.be.equal( @@ -1632,11 +1869,11 @@ describe("Redemption", () => { ) // P2WPKH output (change) - // The value of fee should be the fee share of the (only) redeem output - // which is 1400 - // The output value should be main UTXO input value - fee - the - // value of the output, which is 1429580 = 1446580 - 1400 - 15600 - expect(changeOutput.value).to.be.equal(1429580) + // The value of fee should be the sum of fee share of all redeem outputs + // which is 1100 + 900 + 1000 + 1400 = 4400 + // The output value should be main UTXO input value - fee - sum of all + // outputs, which is 1375180 = 1429580 - 4400 - (15900 + 11300 + 9900 + 12900) + expect(changeOutput.value).to.be.equal(1375180) // The output script should correspond to: // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 expect(changeOutput.script).to.be.equal( @@ -1655,19 +1892,138 @@ describe("Redemption", () => { it("should return the proper new main UTXO", () => { const expectedNewMainUtxo = { transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1429580), + outputIndex: 4, + value: BigNumber.from(1375180), } - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) }) - } - ) + }) + }) + + describe("when the change output is P2PKH", () => { + // The only difference between redemption transactions with P2PKH + // change outputs and P2WPKH change outputs is the output type itself. + // Therefore the tests for creating transactions with P2PKH are + // limited to one single test case as more complicated scenarios are + // covered for P2WPKH change output tests. + const data: RedemptionTestData = + singleP2SHRedemptionWithNonWitnessChange + + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo | undefined + let transaction: BitcoinRawTx + + beforeEach(async () => { + const redemptionRequests = data.pendingRedemptions.map( + (redemption) => redemption.pendingRedemption + ) + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.redemption.assembleTransaction( + walletPrivateKey, + data.mainUtxo, + redemptionRequests + )) + }) + + it("should return transaction with proper structure", async () => { + // Compare HEXes. + expect(transaction).to.be.eql( + data.expectedRedemption.transaction + ) + + // Convert raw transaction to JSON to make detailed comparison. + const buffer = Buffer.from(transaction.transactionHex, "hex") + const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + + expect(txJSON.hash).to.be.equal( + data.expectedRedemption.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) + + const input = txJSON.inputs[0] + + expect(input.prevout.hash).to.be.equal( + data.mainUtxo.transactionHash.toString() + ) + expect(input.prevout.index).to.be.equal( + data.mainUtxo.outputIndex + ) + // Transaction should be signed but this is SegWit input so the `script` + // field should be empty and the `witness` field should be filled instead. + expect(input.script.length).to.be.equal(0) + expect(input.witness.length).to.be.greaterThan(0) + expect(input.address).to.be.equal(p2wpkhWalletAddress) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(2) + + const p2shOutput = txJSON.outputs[0] + const changeOutput = txJSON.outputs[1] + + // P2SH output + // The output value should be `requestedAmount` - `txFee` - `treasuryFee` + // which is 12000 - 1200 - 1000 = 9800 + expect(p2shOutput.value).to.be.equal(9800) + // The output script should correspond to: + // OP_HASH160 0x14 0x3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL + expect(p2shOutput.script).to.be.equal( + "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" + ) + // The output address should be P2SH + expect(p2shOutput.address).to.be.equal( + "2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C" + ) + + // P2PKH output (change) + // The value of fee should be the fee share of the (only) redeem output + // which is 1200 + // The output value should be main UTXO input value - fee - the value + // of the redeem output, which is 1364180 = 1375180 - 1200 - 9800 + expect(changeOutput.value).to.be.equal(1364180) + // The output script should correspond to: + // OP_DUP OP_HASH160 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + // OP_EQUALVERIFY OP_CHECKSIG + expect(changeOutput.script).to.be.equal( + "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac" + ) + // The change output address should be the P2PKH address of the wallet + expect(changeOutput.address).to.be.equal(p2pkhWalletAddress) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + data.expectedRedemption.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: data.expectedRedemption.transactionHash, + outputIndex: 1, + value: BigNumber.from(1364180), + } + + expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) + }) + }) }) - context("when there are multiple redeemers", () => { - const data: RedemptionTestData = - multipleRedemptionsWithWitnessChange + context("when there is no change UTXO created", () => { + const data: RedemptionTestData = multipleRedemptionsWithoutChange let transactionHash: BitcoinTxHash let newMainUtxo: BitcoinUtxo | undefined @@ -1678,15 +2034,20 @@ describe("Redemption", () => { (redemption) => redemption.pendingRedemption ) + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + data.witness + ) + ;({ transactionHash, newMainUtxo, rawTransaction: transaction, - } = await assembleRedemptionTransaction( + } = await walletTx.redemption.assembleTransaction( walletPrivateKey, data.mainUtxo, - redemptionRequests, - data.witness + redemptionRequests )) }) @@ -1719,84 +2080,39 @@ describe("Redemption", () => { expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(5) + expect(txJSON.outputs.length).to.be.equal(2) const p2pkhOutput = txJSON.outputs[0] const p2wpkhOutput = txJSON.outputs[1] - const p2shOutput = txJSON.outputs[2] - const p2wshOutput = txJSON.outputs[3] - const changeOutput = txJSON.outputs[4] // P2PKH output // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 18000 - 1100 - 1000 = 15900 - expect(p2pkhOutput.value).to.be.equal(15900) + // which is 6000 - 800 - 0 = 5200 + expect(p2pkhOutput.value).to.be.equal(5200) // The output script should correspond to: // OP_DUP OP_HASH160 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 // OP_EQUALVERIFY OP_CHECKSIG expect(p2pkhOutput.script).to.be.equal( "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac" ) - // The output address should be P2PKH + // The output address should be P2PK expect(p2pkhOutput.address).to.be.equal( "mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5" ) // P2WPKH output // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 13000 - 900 - 800 = 11300 - expect(p2wpkhOutput.value).to.be.equal(11300) + // which is 4000 - 900 - 0 = 3100 + expect(p2wpkhOutput.value).to.be.equal(3100) // The output script should correspond to: - // OP_0 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 + // OP_0 0x14 0x4bf9ffb7ae0f8b0f5a622b154aca829126f6e769 expect(p2wpkhOutput.script).to.be.equal( - "00144130879211c54df460e484ddf9aac009cb38ee74" + "00144bf9ffb7ae0f8b0f5a622b154aca829126f6e769" ) - // The output address should be P2WPKH + // The output address should be P2PKH expect(p2wpkhOutput.address).to.be.equal( - "tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l" - ) - - // P2SH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 12000 - 1000 - 1100 = 9900 - expect(p2shOutput.value).to.be.equal(9900) - // The output script should correspond to: - // OP_HASH160 0x14 0x3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL - expect(p2shOutput.script).to.be.equal( - "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" - ) - // The output address should be P2SH - expect(p2shOutput.address).to.be.equal( - "2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C" - ) - - // P2WSH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 15000 - 1400 - 700 = 12900 - expect(p2wshOutput.value).to.be.equal(12900) - // The output script should correspond to: - // OP_0 0x20 0x86a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96 - expect(p2wshOutput.script).to.be.equal( - "002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96" + "tb1qf0ulldawp79s7knz9v254j5zjyn0demfx2d0xx" ) - // The output address should be P2WSH - expect(p2wshOutput.address).to.be.equal( - "tb1qs63s8nwjut4tr5t8nudgzwp4m3dpkefjzpmumn90pruce0cye2tq2jkq0y" - ) - - // P2WPKH output (change) - // The value of fee should be the sum of fee share of all redeem outputs - // which is 1100 + 900 + 1000 + 1400 = 4400 - // The output value should be main UTXO input value - fee - sum of all - // outputs, which is 1375180 = 1429580 - 4400 - (15900 + 11300 + 9900 + 12900) - expect(changeOutput.value).to.be.equal(1375180) - // The output script should correspond to: - // OP_0 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - expect(changeOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The change output address should be the P2WPKH address of the wallet - expect(changeOutput.address).to.be.equal(p2wpkhWalletAddress) }) it("should return the proper transaction hash", async () => { @@ -1805,329 +2121,126 @@ describe("Redemption", () => { ) }) - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 4, - value: BigNumber.from(1375180), - } - - expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) + it("should not return the new main UTXO", () => { + expect(newMainUtxo).to.be.undefined }) }) }) - describe("when the change output is P2PKH", () => { - // The only difference between redemption transactions with P2PKH - // change outputs and P2WPKH change outputs is the output type itself. - // Therefore the tests for creating transactions with P2PKH are - // limited to one single test case as more complicated scenarios are - // covered for P2WPKH change output tests. + context("when there are no redemption requests provided", () => { const data: RedemptionTestData = - singleP2SHRedemptionWithNonWitnessChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - let transaction: BitcoinRawTx + singleP2PKHRedemptionWithWitnessChange - beforeEach(async () => { - const redemptionRequests = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption - ) - - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleRedemptionTransaction( - walletPrivateKey, - data.mainUtxo, - redemptionRequests, + it("should revert", async () => { + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, data.witness - )) - }) - - it("should return transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql(data.expectedRedemption.transaction) - - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") - - expect(txJSON.hash).to.be.equal( - data.expectedRedemption.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) - - const input = txJSON.inputs[0] - - expect(input.prevout.hash).to.be.equal( - data.mainUtxo.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal(data.mainUtxo.outputIndex) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) - - const p2shOutput = txJSON.outputs[0] - const changeOutput = txJSON.outputs[1] - - // P2SH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 12000 - 1200 - 1000 = 9800 - expect(p2shOutput.value).to.be.equal(9800) - // The output script should correspond to: - // OP_HASH160 0x14 0x3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL - expect(p2shOutput.script).to.be.equal( - "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" - ) - // The output address should be P2SH - expect(p2shOutput.address).to.be.equal( - "2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C" - ) - - // P2PKH output (change) - // The value of fee should be the fee share of the (only) redeem output - // which is 1200 - // The output value should be main UTXO input value - fee - the value - // of the redeem output, which is 1364180 = 1375180 - 1200 - 9800 - expect(changeOutput.value).to.be.equal(1364180) - // The output script should correspond to: - // OP_DUP OP_HASH160 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - // OP_EQUALVERIFY OP_CHECKSIG - expect(changeOutput.script).to.be.equal( - "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac" - ) - // The change output address should be the P2PKH address of the wallet - expect(changeOutput.address).to.be.equal(p2pkhWalletAddress) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: data.expectedRedemption.transactionHash, - outputIndex: 1, - value: BigNumber.from(1364180), - } - expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) + await expect( + walletTx.redemption.assembleTransaction( + walletPrivateKey, + data.mainUtxo, + [] // empty list of redemption requests + ) + ).to.be.rejectedWith("There must be at least one request to redeem") }) }) }) + }) + }) - context("when there is no change UTXO created", () => { - const data: RedemptionTestData = multipleRedemptionsWithoutChange - - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo | undefined - let transaction: BitcoinRawTx - - beforeEach(async () => { - const redemptionRequests = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption - ) - - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await assembleRedemptionTransaction( - walletPrivateKey, - data.mainUtxo, - redemptionRequests, - data.witness - )) - }) + describe("Spv", () => { + describe("submitRedemptionProof", () => { + const mainUtxo = { + transactionHash: BitcoinTxHash.from( + "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" + ), + outputIndex: 1, + value: BigNumber.from(1429580), + } - it("should return transaction with proper structure", async () => { - // Compare HEXes. - expect(transaction).to.be.eql(data.expectedRedemption.transaction) + let bitcoinClient: MockBitcoinClient + let tbtcContracts: MockTBTCContracts + let maintenanceService: MaintenanceService - // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + beforeEach(async () => { + bcoin.set("testnet") - expect(txJSON.hash).to.be.equal( - data.expectedRedemption.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) + maintenanceService = new MaintenanceService( + tbtcContracts, + bitcoinClient + ) - const input = txJSON.inputs[0] + const transactionHash = + redemptionProof.bitcoinChainData.transaction.transactionHash - expect(input.prevout.hash).to.be.equal( - data.mainUtxo.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal(data.mainUtxo.outputIndex) - // Transaction should be signed but this is SegWit input so the `script` - // field should be empty and the `witness` field should be filled instead. - expect(input.script.length).to.be.equal(0) - expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(2) - - const p2pkhOutput = txJSON.outputs[0] - const p2wpkhOutput = txJSON.outputs[1] - - // P2PKH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 6000 - 800 - 0 = 5200 - expect(p2pkhOutput.value).to.be.equal(5200) - // The output script should correspond to: - // OP_DUP OP_HASH160 0x14 0x4130879211c54df460e484ddf9aac009cb38ee74 - // OP_EQUALVERIFY OP_CHECKSIG - expect(p2pkhOutput.script).to.be.equal( - "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac" - ) - // The output address should be P2PK - expect(p2pkhOutput.address).to.be.equal( - "mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5" - ) + const transactions = new Map() + transactions.set( + transactionHash.toString(), + redemptionProof.bitcoinChainData.transaction + ) + bitcoinClient.transactions = transactions - // P2WPKH output - // The output value should be `requestedAmount` - `txFee` - `treasuryFee` - // which is 4000 - 900 - 0 = 3100 - expect(p2wpkhOutput.value).to.be.equal(3100) - // The output script should correspond to: - // OP_0 0x14 0x4bf9ffb7ae0f8b0f5a622b154aca829126f6e769 - expect(p2wpkhOutput.script).to.be.equal( - "00144bf9ffb7ae0f8b0f5a622b154aca829126f6e769" - ) - // The output address should be P2PKH - expect(p2wpkhOutput.address).to.be.equal( - "tb1qf0ulldawp79s7knz9v254j5zjyn0demfx2d0xx" - ) - }) + const rawTransactions = new Map() + rawTransactions.set( + transactionHash.toString(), + redemptionProof.bitcoinChainData.rawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - data.expectedRedemption.transactionHash - ) - }) + bitcoinClient.latestHeight = + redemptionProof.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = + redemptionProof.bitcoinChainData.headersChain + bitcoinClient.transactionMerkle = + redemptionProof.bitcoinChainData.transactionMerkleBranch + const confirmations = new Map() + confirmations.set( + transactionHash.toString(), + redemptionProof.bitcoinChainData.accumulatedTxConfirmations + ) + bitcoinClient.confirmations = confirmations - it("should not return the new main UTXO", () => { - expect(newMainUtxo).to.be.undefined - }) + await maintenanceService.spv.submitRedemptionProof( + transactionHash, + mainUtxo, + walletPublicKey + ) }) - }) - - context("when there are no redemption requests provided", () => { - const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange - it("should revert", async () => { - await expect( - assembleRedemptionTransaction( - walletPrivateKey, - data.mainUtxo, - [], // empty list of redemption requests - data.witness - ) - ).to.be.rejectedWith("There must be at least one request to redeem") + it("should submit redemption proof with correct arguments", () => { + const bridgeLog = tbtcContracts.bridge.redemptionProofLog + expect(bridgeLog.length).to.equal(1) + expect(bridgeLog[0].mainUtxo).to.equal(mainUtxo) + expect(bridgeLog[0].walletPublicKey).to.equal( + redemptionProof.expectedRedemptionProof.walletPublicKey + ) + expect(bridgeLog[0].redemptionTx).to.deep.equal( + redemptionProof.expectedRedemptionProof.redemptionTx + ) + expect(bridgeLog[0].redemptionProof.txIndexInBlock).to.deep.equal( + redemptionProof.expectedRedemptionProof.redemptionProof.txIndexInBlock + ) + expect(bridgeLog[0].redemptionProof.merkleProof).to.deep.equal( + redemptionProof.expectedRedemptionProof.redemptionProof.merkleProof + ) + expect(bridgeLog[0].redemptionProof.bitcoinHeaders).to.deep.equal( + redemptionProof.expectedRedemptionProof.redemptionProof.bitcoinHeaders + ) }) }) }) - - describe("submitRedemptionProof", () => { - const mainUtxo = { - transactionHash: BitcoinTxHash.from( - "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" - ), - outputIndex: 1, - value: BigNumber.from(1429580), - } - - let bitcoinClient: MockBitcoinClient - let bridge: MockBridge - - beforeEach(async () => { - bcoin.set("testnet") - - bitcoinClient = new MockBitcoinClient() - bridge = new MockBridge() - - const transactionHash = - redemptionProof.bitcoinChainData.transaction.transactionHash - - const transactions = new Map() - transactions.set( - transactionHash.toString(), - redemptionProof.bitcoinChainData.transaction - ) - bitcoinClient.transactions = transactions - - const rawTransactions = new Map() - rawTransactions.set( - transactionHash.toString(), - redemptionProof.bitcoinChainData.rawTransaction - ) - bitcoinClient.rawTransactions = rawTransactions - - bitcoinClient.latestHeight = - redemptionProof.bitcoinChainData.latestBlockHeight - bitcoinClient.headersChain = redemptionProof.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = - redemptionProof.bitcoinChainData.transactionMerkleBranch - const confirmations = new Map() - confirmations.set( - transactionHash.toString(), - redemptionProof.bitcoinChainData.accumulatedTxConfirmations - ) - bitcoinClient.confirmations = confirmations - - await submitRedemptionProof( - transactionHash, - mainUtxo, - walletPublicKey, - bridge, - bitcoinClient - ) - }) - - it("should submit redemption proof with correct arguments", () => { - const bridgeLog = bridge.redemptionProofLog - expect(bridgeLog.length).to.equal(1) - expect(bridgeLog[0].mainUtxo).to.equal(mainUtxo) - expect(bridgeLog[0].walletPublicKey).to.equal( - redemptionProof.expectedRedemptionProof.walletPublicKey - ) - expect(bridgeLog[0].redemptionTx).to.deep.equal( - redemptionProof.expectedRedemptionProof.redemptionTx - ) - expect(bridgeLog[0].redemptionProof.txIndexInBlock).to.deep.equal( - redemptionProof.expectedRedemptionProof.redemptionProof.txIndexInBlock - ) - expect(bridgeLog[0].redemptionProof.merkleProof).to.deep.equal( - redemptionProof.expectedRedemptionProof.redemptionProof.merkleProof - ) - expect(bridgeLog[0].redemptionProof.bitcoinHeaders).to.deep.equal( - redemptionProof.expectedRedemptionProof.redemptionProof.bitcoinHeaders - ) - }) - }) }) async function runRedemptionScenario( walletPrivKey: string, bitcoinClient: MockBitcoinClient, - bridge: MockBridge, + tbtcContracts: MockTBTCContracts, data: RedemptionTestData ): Promise<{ transactionHash: BitcoinTxHash @@ -2139,7 +2252,7 @@ async function runRedemptionScenario( }) bitcoinClient.rawTransactions = rawTransactions - bridge.setPendingRedemptions( + tbtcContracts.bridge.setPendingRedemptions( new Map( data.pendingRedemptions.map((redemption) => [ redemption.redemptionKey, @@ -2152,12 +2265,11 @@ async function runRedemptionScenario( (redemption) => redemption.pendingRedemption.redeemerOutputScript ) - return submitRedemptionTransaction( - bitcoinClient, - bridge, + const walletTx = new WalletTx(tbtcContracts, bitcoinClient, data.witness) + + return walletTx.redemption.submitTransaction( walletPrivKey, data.mainUtxo, - redeemerOutputScripts, - data.witness + redeemerOutputScripts ) } From 550486d419d2f31ca4d2c82cb6612ed351667445 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 29 Sep 2023 14:12:52 +0200 Subject: [PATCH 21/43] Skip Electrum test suite This test suite depends on external Electrum servers and is prone to their health fluctuations. We observed such fluctuations now, and we are temporarily skipping their default run to avoid loosing time. --- typescript/test/electrum.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index 6d8d80020..eccfe1056 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -84,7 +84,7 @@ const testnetCredentials: ElectrumCredentials[] = [ * out of scope of this suite. The `broadcast` function was tested manually * though. */ -describe("Electrum", () => { +describe.skip("Electrum", () => { testnetCredentials.forEach((credentials) => { describe(`${credentials.protocol}://${credentials.host}:${credentials.port}`, async () => { let electrumClient: ElectrumClient From 6628c09f00709497598736c1f969267aa82bdba1 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Fri, 29 Sep 2023 17:20:43 +0200 Subject: [PATCH 22/43] Add `TBTC` entrypoint component and embed chain configs Here we add the `TBTC` entrypoint component allowing to easily set up the SDK to work with supported Ethereum-based and Bitcoin networks. --- typescript/src/index.ts | 5 + typescript/src/lib/contracts/bridge.ts | 5 + typescript/src/lib/contracts/tbtc-token.ts | 6 + .../src/lib/contracts/wallet-registry.ts | 5 + typescript/src/lib/electrum/client.ts | 25 + typescript/src/lib/electrum/urls/mainnet.json | 8 + typescript/src/lib/electrum/urls/testnet.json | 3 + .../lib/ethereum/artifacts/goerli/Bridge.json | 2637 +++++++++++++++++ .../ethereum/artifacts/goerli/TBTCToken.json | 763 +++++ .../ethereum/artifacts/goerli/TBTCVault.json | 1606 ++++++++++ .../artifacts/goerli/WalletRegistry.json | 1962 ++++++++++++ .../ethereum/artifacts/mainnet/Bridge.json | 2624 ++++++++++++++++ .../ethereum/artifacts/mainnet/TBTCToken.json | 762 +++++ .../ethereum/artifacts/mainnet/TBTCVault.json | 1606 ++++++++++ .../artifacts/mainnet/WalletRegistry.json | 1962 ++++++++++++ typescript/src/lib/ethereum/bridge.ts | 37 +- typescript/src/lib/ethereum/index.ts | 90 + typescript/src/lib/ethereum/tbtc-token.ts | 39 +- typescript/src/lib/ethereum/tbtc-vault.ts | 29 +- .../src/lib/ethereum/wallet-registry.ts | 43 +- .../maintenance/maintenance-service.ts | 1 - typescript/src/services/tbtc.ts | 96 + typescript/test/utils/mock-bridge.ts | 4 + typescript/test/utils/mock-tbtc-token.ts | 7 +- typescript/test/utils/mock-wallet-registry.ts | 6 + 25 files changed, 14315 insertions(+), 16 deletions(-) create mode 100644 typescript/src/lib/electrum/urls/mainnet.json create mode 100644 typescript/src/lib/electrum/urls/testnet.json create mode 100644 typescript/src/lib/ethereum/artifacts/goerli/Bridge.json create mode 100644 typescript/src/lib/ethereum/artifacts/goerli/TBTCToken.json create mode 100644 typescript/src/lib/ethereum/artifacts/goerli/TBTCVault.json create mode 100644 typescript/src/lib/ethereum/artifacts/goerli/WalletRegistry.json create mode 100644 typescript/src/lib/ethereum/artifacts/mainnet/Bridge.json create mode 100644 typescript/src/lib/ethereum/artifacts/mainnet/TBTCToken.json create mode 100644 typescript/src/lib/ethereum/artifacts/mainnet/TBTCVault.json create mode 100644 typescript/src/lib/ethereum/artifacts/mainnet/WalletRegistry.json create mode 100644 typescript/src/services/tbtc.ts diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 197a34d8f..2ee4bcafd 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -1,9 +1,14 @@ +// Export shared library modules. export * from "./lib/bitcoin" export * from "./lib/contracts" export * from "./lib/electrum" export * from "./lib/ethereum" export * from "./lib/utils" +// Export feature modules (services). export * from "./services/deposits" export * from "./services/maintenance" export * from "./services/redemptions" + +// Export the entrypoint module. +export * from "./services/tbtc" diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index 52c06f3bd..d78499c55 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -14,6 +14,11 @@ import { WalletRegistry } from "./wallet-registry" * Interface for communication with the Bridge on-chain contract. */ export interface Bridge { + /** + * Gets the chain-specific identifier of this contract. + */ + getChainIdentifier(): ChainIdentifier + /** * Get emitted DepositRevealed events. * @see GetEventsFunction diff --git a/typescript/src/lib/contracts/tbtc-token.ts b/typescript/src/lib/contracts/tbtc-token.ts index 7770510dd..b78ef1823 100644 --- a/typescript/src/lib/contracts/tbtc-token.ts +++ b/typescript/src/lib/contracts/tbtc-token.ts @@ -1,11 +1,17 @@ import { BigNumber } from "ethers" import { BitcoinUtxo } from "../bitcoin" import { Hex } from "../utils" +import { ChainIdentifier } from "./chain-identifier" /** * Interface for communication with the TBTC v2 token on-chain contract. */ export interface TBTCToken { + /** + * Gets the chain-specific identifier of this contract. + */ + getChainIdentifier(): ChainIdentifier + /** * Gets the total supply of the TBTC v2 token. The returned value is in * ERC 1e18 precision, it has to be converted before using as Bitcoin value diff --git a/typescript/src/lib/contracts/wallet-registry.ts b/typescript/src/lib/contracts/wallet-registry.ts index 417e9c745..bd18cde29 100644 --- a/typescript/src/lib/contracts/wallet-registry.ts +++ b/typescript/src/lib/contracts/wallet-registry.ts @@ -7,6 +7,11 @@ import { ChainIdentifier } from "./chain-identifier" * Interface for communication with the WalletRegistry on-chain contract. */ export interface WalletRegistry { + /** + * Gets the chain-specific identifier of this contract. + */ + getChainIdentifier(): ChainIdentifier + /** * Gets the public key for the given wallet. * @param walletID ID of the wallet. diff --git a/typescript/src/lib/electrum/client.ts b/typescript/src/lib/electrum/client.ts index c24b68d57..dd0b10aab 100644 --- a/typescript/src/lib/electrum/client.ts +++ b/typescript/src/lib/electrum/client.ts @@ -17,6 +17,9 @@ import { BigNumber, utils } from "ethers" import { URL } from "url" import { backoffRetrier, Hex, RetrierFn } from "../utils" +import MainnetElectrumUrls from "./urls/mainnet.json" +import TestnetElectrumUrls from "./urls/testnet.json" + /** * Represents a set of credentials required to establish an Electrum connection. */ @@ -110,6 +113,28 @@ export class ElectrumClient implements BitcoinClient { ) } + /** + * Creates an Electrum client instance using a default config for the given + * Bitcoin network. + * @param network Bitcoin network the instance should be created for. + * @returns Electrum client instance. + */ + static fromDefaultConfig(network: BitcoinNetwork): ElectrumClient { + let file + switch (network) { + case BitcoinNetwork.Mainnet: + file = MainnetElectrumUrls + break + case BitcoinNetwork.Testnet: + file = TestnetElectrumUrls + break + default: + throw new Error("No default Electrum for given network") + } + + return ElectrumClient.fromUrl(file.urls) + } + /** * Create Electrum credentials by parsing an URL. * @param url - URL to be parsed. diff --git a/typescript/src/lib/electrum/urls/mainnet.json b/typescript/src/lib/electrum/urls/mainnet.json new file mode 100644 index 000000000..8c36cb6f0 --- /dev/null +++ b/typescript/src/lib/electrum/urls/mainnet.json @@ -0,0 +1,8 @@ +{ + "urls": [ + "wss://electrumx-server.tbtc.network:8443", + "wss://electrum.boar.network:2083", + "wss://bitcoin.threshold.p2p.org:50004", + "wss://electrumx.prod-utility-eks-us-west-2.staked.cloud:443" + ] +} diff --git a/typescript/src/lib/electrum/urls/testnet.json b/typescript/src/lib/electrum/urls/testnet.json new file mode 100644 index 000000000..fb50c0bed --- /dev/null +++ b/typescript/src/lib/electrum/urls/testnet.json @@ -0,0 +1,3 @@ +{ + "urls": ["wss://electrumx-server.test.tbtc.network:8443"] +} diff --git a/typescript/src/lib/ethereum/artifacts/goerli/Bridge.json b/typescript/src/lib/ethereum/artifacts/goerli/Bridge.json new file mode 100644 index 000000000..20fa37e56 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/goerli/Bridge.json @@ -0,0 +1,2637 @@ +{ + "address": "0x0Cad3257C4B7ec6de1f6926Fbf5714255a6632c3", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "depositDustThreshold", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "depositTreasuryFeeDivisor", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "depositTxMaxFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "depositRevealAheadPeriod", + "type": "uint32" + } + ], + "name": "DepositParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "amount", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes8", + "name": "blindingFactor", + "type": "bytes8" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes20", + "name": "refundPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "refundLocktime", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "DepositRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sweepTxHash", + "type": "bytes32" + } + ], + "name": "DepositsSwept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sighash", + "type": "bytes32" + } + ], + "name": "FraudChallengeDefeatTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sighash", + "type": "bytes32" + } + ], + "name": "FraudChallengeDefeated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sighash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "FraudChallengeSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint96", + "name": "fraudChallengeDepositAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fraudChallengeDefeatTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fraudSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fraudNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "FraudParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldGovernance", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "GovernanceTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "movingFundsTxHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTxOutputIndex", + "type": "uint32" + } + ], + "name": "MovedFundsSweepTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sweepTxHash", + "type": "bytes32" + } + ], + "name": "MovedFundsSwept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "MovingFundsBelowDustReported", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes20[]", + "name": "targetWallets", + "type": "bytes20[]" + }, + { + "indexed": false, + "internalType": "address", + "name": "submitter", + "type": "address" + } + ], + "name": "MovingFundsCommitmentSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "movingFundsTxHash", + "type": "bytes32" + } + ], + "name": "MovingFundsCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "movingFundsTxMaxTotalFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "movingFundsDustThreshold", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTimeoutResetDelay", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "movingFundsTimeoutSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTimeoutNotifierRewardMultiplier", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "movingFundsCommitmentGasOffset", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "movedFundsSweepTxMaxTotalFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movedFundsSweepTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "movedFundsSweepTimeoutSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movedFundsSweepTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "MovingFundsParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "MovingFundsTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "MovingFundsTimeoutReset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "NewWalletRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "NewWalletRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionDustThreshold", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionTreasuryFeeDivisor", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionTxMaxFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionTxMaxTotalFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "redemptionTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "redemptionTimeoutSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "redemptionTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "RedemptionParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "requestedAmount", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "txMaxFee", + "type": "uint64" + } + ], + "name": "RedemptionRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + } + ], + "name": "RedemptionTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "redemptionTxHash", + "type": "bytes32" + } + ], + "name": "RedemptionsCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "spvMaintainer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "SpvMaintainerStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "TreasuryUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "VaultStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletClosing", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletMovingFunds", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "walletCreationPeriod", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletCreationMinBtcBalance", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletCreationMaxBtcBalance", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletClosureMinBtcBalance", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "walletMaxAge", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletMaxBtcTransfer", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "walletClosingPeriod", + "type": "uint32" + } + ], + "name": "WalletParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletTerminated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "name": "__ecdsaWalletCreatedCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "name": "__ecdsaWalletHeartbeatFailedCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "activeWalletPubKeyHash", + "outputs": [ + { + "internalType": "bytes20", + "name": "", + "type": "bytes20" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "contractReferences", + "outputs": [ + { + "internalType": "contract Bank", + "name": "bank", + "type": "address" + }, + { + "internalType": "contract IRelay", + "name": "relay", + "type": "address" + }, + { + "internalType": "contract IWalletRegistry", + "name": "ecdsaWalletRegistry", + "type": "address" + }, + { + "internalType": "contract ReimbursementPool", + "name": "reimbursementPool", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "preimage", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "witness", + "type": "bool" + } + ], + "name": "defeatFraudChallenge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "heartbeatMessage", + "type": "bytes" + } + ], + "name": "defeatFraudChallengeWithHeartbeat", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositParameters", + "outputs": [ + { + "internalType": "uint64", + "name": "depositDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "depositRevealAheadPeriod", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "revealedAt", + "type": "uint32" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "sweptAt", + "type": "uint32" + } + ], + "internalType": "struct Deposit.DepositRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "challengeKey", + "type": "uint256" + } + ], + "name": "fraudChallenges", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "challenger", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositAmount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "reportedAt", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "resolved", + "type": "bool" + } + ], + "internalType": "struct Fraud.FraudChallenge", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fraudParameters", + "outputs": [ + { + "internalType": "uint96", + "name": "fraudChallengeDepositAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudChallengeDefeatTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "fraudSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudNotifierRewardMultiplier", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_bank", + "type": "address" + }, + { + "internalType": "address", + "name": "_relay", + "type": "address" + }, + { + "internalType": "address", + "name": "_treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "_ecdsaWalletRegistry", + "type": "address" + }, + { + "internalType": "address payable", + "name": "_reimbursementPool", + "type": "address" + }, + { + "internalType": "uint96", + "name": "_txProofDifficultyFactor", + "type": "uint96" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "isVaultTrusted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liveWalletsCount", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestKey", + "type": "uint256" + } + ], + "name": "movedFundsSweepRequests", + "outputs": [ + { + "components": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "uint64", + "name": "value", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "createdAt", + "type": "uint32" + }, + { + "internalType": "enum MovingFunds.MovedFundsSweepRequestState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct MovingFunds.MovedFundsSweepRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "movingFundsParameters", + "outputs": [ + { + "internalType": "uint64", + "name": "movingFundsTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "movingFundsDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutResetDelay", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movingFundsTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutNotifierRewardMultiplier", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "movingFundsCommitmentGasOffset", + "type": "uint16" + }, + { + "internalType": "uint64", + "name": "movedFundsSweepTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movedFundsSweepTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "bytes", + "name": "preimageSha256", + "type": "bytes" + } + ], + "name": "notifyFraudChallengeDefeatTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "movingFundsTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "movingFundsTxOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + } + ], + "name": "notifyMovedFundsSweepTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + } + ], + "name": "notifyMovingFundsBelowDust", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + } + ], + "name": "notifyMovingFundsTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + } + ], + "name": "notifyRedemptionTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "walletMainUtxo", + "type": "tuple" + } + ], + "name": "notifyWalletCloseable", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "notifyWalletClosingPeriodElapsed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redemptionKey", + "type": "uint256" + } + ], + "name": "pendingRedemptions", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestedAmount", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "txMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "requestedAt", + "type": "uint32" + } + ], + "internalType": "struct Redemption.RedemptionRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "balanceOwner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "redemptionData", + "type": "bytes" + } + ], + "name": "receiveBalanceApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "redemptionParameters", + "outputs": [ + { + "internalType": "uint64", + "name": "redemptionDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "redemptionTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "redemptionTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "redemptionTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "activeWalletMainUtxo", + "type": "tuple" + } + ], + "name": "requestNewWallet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + } + ], + "name": "requestRedemption", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "resetMovingFundsTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "fundingTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + }, + { + "internalType": "bytes8", + "name": "blindingFactor", + "type": "bytes8" + }, + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "bytes20", + "name": "refundPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "bytes4", + "name": "refundLocktime", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "internalType": "struct Deposit.DepositRevealInfo", + "name": "reveal", + "type": "tuple" + } + ], + "name": "revealDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spvMaintainer", + "type": "address" + }, + { + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "setSpvMaintainerStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "setVaultStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "utxoKey", + "type": "uint256" + } + ], + "name": "spentMainUTXOs", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "sweepTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "sweepProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "submitDepositSweepProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "preimageSha256", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + } + ], + "internalType": "struct BitcoinTx.RSVSignature", + "name": "signature", + "type": "tuple" + } + ], + "name": "submitFraudChallenge", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "sweepTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "sweepProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + } + ], + "name": "submitMovedFundsSweepProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "walletMainUtxo", + "type": "tuple" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "walletMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes20[]", + "name": "targetWallets", + "type": "bytes20[]" + } + ], + "name": "submitMovingFundsCommitment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "movingFundsTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "movingFundsProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "submitMovingFundsProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "redemptionTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "redemptionProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "submitRedemptionProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redemptionKey", + "type": "uint256" + } + ], + "name": "timedOutRedemptions", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestedAmount", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "txMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "requestedAt", + "type": "uint32" + } + ], + "internalType": "struct Redemption.RedemptionRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "txProofDifficultyFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "depositDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "depositRevealAheadPeriod", + "type": "uint32" + } + ], + "name": "updateDepositParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "fraudChallengeDepositAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudChallengeDefeatTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "fraudSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "updateFraudParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "movingFundsTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "movingFundsDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutResetDelay", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movingFundsTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutNotifierRewardMultiplier", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "movingFundsCommitmentGasOffset", + "type": "uint16" + }, + { + "internalType": "uint64", + "name": "movedFundsSweepTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movedFundsSweepTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "updateMovingFundsParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "redemptionDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "redemptionTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "redemptionTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "redemptionTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "updateRedemptionParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRelay", + "name": "relay", + "type": "address" + } + ], + "name": "updateRelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "updateTreasury", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "walletCreationPeriod", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletCreationMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletCreationMaxBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletClosureMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletMaxAge", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletMaxBtcTransfer", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletClosingPeriod", + "type": "uint32" + } + ], + "name": "updateWalletParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "walletParameters", + "outputs": [ + { + "internalType": "uint32", + "name": "walletCreationPeriod", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletCreationMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletCreationMaxBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletClosureMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletMaxAge", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletMaxBtcTransfer", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletClosingPeriod", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "wallets", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "mainUtxoHash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "pendingRedemptionsValue", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "createdAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "movingFundsRequestedAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "closingStartedAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "pendingMovedFundsSweepRequestsCount", + "type": "uint32" + }, + { + "internalType": "enum Wallets.WalletState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "movingFundsTargetWalletsCommitmentHash", + "type": "bytes32" + } + ], + "internalType": "struct Wallets.Wallet", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x0c3003c638a0a8b590f118e99c0112f6d9e0b6b816bd26e8902f3208429f5e92", + "receipt": { + "to": "0x47439cddBEeb66834D07C84dfa16a3F53E7166DB", + "from": "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", + "contractAddress": null, + "transactionIndex": 7, + "gasUsed": "38760", + "logsBloom": "0x00000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000200000000000000000000000060000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000080000", + "blockHash": "0x42f642f6d86665c26d412806fa7afd2aa048a8abceaa458990d83ab61406dcd4", + "transactionHash": "0x0c3003c638a0a8b590f118e99c0112f6d9e0b6b816bd26e8902f3208429f5e92", + "logs": [ + { + "transactionIndex": 7, + "blockNumber": 8981331, + "transactionHash": "0x0c3003c638a0a8b590f118e99c0112f6d9e0b6b816bd26e8902f3208429f5e92", + "address": "0x0Cad3257C4B7ec6de1f6926Fbf5714255a6632c3", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x00000000000000000000000003d47483486c1d1dfd99faa4b943363d07eafddc" + ], + "data": "0x", + "logIndex": 11, + "blockHash": "0x42f642f6d86665c26d412806fa7afd2aa048a8abceaa458990d83ab61406dcd4" + } + ], + "blockNumber": 8981331, + "cumulativeGasUsed": "1188482", + "status": 1, + "byzantium": true + }, + "numDeployments": 3, + "libraries": { + "Deposit": "0xc6911F875E58513B3471678Bf804f56ea78639d1", + "DepositSweep": "0x0e8DE6422034B3E26e23FB201F510af783719699", + "Redemption": "0x9c7f623879aD9CC197A615DccDa2Ef5128B8b8F0", + "Wallets": "0xE4Bc89b7aB1cFD7827D2b9A101546fCcBC599b1C", + "Fraud": "0x64E8AeA9b1dF57df4e53e762ffd45Be1C7F1e211", + "MovingFunds": "0xa572224301529Ea81AC11d2aF07514F1AD647Fd4" + }, + "implementation": "0x03d47483486c1d1DFD99FaA4B943363D07EAFDDC", + "devdoc": "Contract deployed as upgradable proxy" +} diff --git a/typescript/src/lib/ethereum/artifacts/goerli/TBTCToken.json b/typescript/src/lib/ethereum/artifacts/goerli/TBTCToken.json new file mode 100644 index 000000000..d56505e19 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/goerli/TBTCToken.json @@ -0,0 +1,763 @@ +{ + "address": "0x679874fBE6D4E7Cc54A59e315FF1eB266686a937", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "approveAndCall", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cachedChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cachedDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xc84cbf9e28bd17faf747312883a91aea795c6e07dc66e5fe8b7339a3963e368d", + "receipt": { + "to": null, + "from": "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", + "contractAddress": "0x679874fBE6D4E7Cc54A59e315FF1eB266686a937", + "transactionIndex": 29, + "gasUsed": "1656167", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000200000200000001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010", + "blockHash": "0x3dfc22aef2e4511f80b1c99fc8c2301313ad753baaf4c6f9946569abb8ebbb49", + "transactionHash": "0xc84cbf9e28bd17faf747312883a91aea795c6e07dc66e5fe8b7339a3963e368d", + "logs": [ + { + "transactionIndex": 29, + "blockNumber": 8364943, + "transactionHash": "0xc84cbf9e28bd17faf747312883a91aea795c6e07dc66e5fe8b7339a3963e368d", + "address": "0x679874fBE6D4E7Cc54A59e315FF1eB266686a937", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000068ad60cc5e8f3b7cc53beab321cf0e6036962dbc" + ], + "data": "0x", + "logIndex": 48, + "blockHash": "0x3dfc22aef2e4511f80b1c99fc8c2301313ad753baaf4c6f9946569abb8ebbb49" + } + ], + "blockNumber": 8364943, + "cumulativeGasUsed": "5222559", + "status": 1, + "byzantium": true + }, + "args": [], + "numDeployments": 1, + "solcInputHash": "f862bd134ddc06352fa5be0644481600", + "metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"approveAndCall\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burnFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedDomainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"approve(address,uint256)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\",\"returns\":{\"_0\":\"True if the operation succeeded.\"}},\"approveAndCall(address,uint256,bytes)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\",\"returns\":{\"_0\":\"True if both approval and `receiveApproval` calls succeeded.\"}},\"burn(uint256)\":{\"details\":\"Requirements: - the caller must have a balance of at least `amount`.\"},\"burnFrom(address,uint256)\":{\"details\":\"Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`.\"},\"mint(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"details\":\"The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"transfer(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferFrom(address,address,uint256)\":{\"details\":\"Requirements: - `spender` and `recipient` cannot be the zero address, - `spender` must have a balance of at least `amount`, - the caller must have allowance for `spender`'s tokens of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"DOMAIN_SEPARATOR()\":{\"notice\":\"Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function.\"},\"PERMIT_TYPEHASH()\":{\"notice\":\"Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function.\"},\"allowance(address,address)\":{\"notice\":\"The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default.\"},\"approve(address,uint256)\":{\"notice\":\"Sets `amount` as the allowance of `spender` over the caller's tokens.\"},\"approveAndCall(address,uint256,bytes)\":{\"notice\":\"Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted.\"},\"balanceOf(address)\":{\"notice\":\"The amount of tokens owned by the given account.\"},\"burn(uint256)\":{\"notice\":\"Destroys `amount` tokens from the caller.\"},\"burnFrom(address,uint256)\":{\"notice\":\"Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"},\"decimals()\":{\"notice\":\"The decimals places of the token.\"},\"mint(address,uint256)\":{\"notice\":\"Creates `amount` tokens and assigns them to `account`, increasing the total supply.\"},\"name()\":{\"notice\":\"The name of the token.\"},\"nonce(address)\":{\"notice\":\"Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"notice\":\"EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction.\"},\"symbol()\":{\"notice\":\"The symbol of the token.\"},\"totalSupply()\":{\"notice\":\"The amount of tokens in existence.\"},\"transfer(address,uint256)\":{\"notice\":\"Moves `amount` tokens from the caller's account to `recipient`.\"},\"transferFrom(address,address,uint256)\":{\"notice\":\"Moves `amount` tokens from `spender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/token/TBTC.sol\":\"TBTC\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":1000},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0x24e0364e503a9bbde94c715d26573a76f14cd2a202d45f96f52134ab806b67b9\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 amount\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x9750c6b834f7b43000631af5cc30001c5f547b3ceb3635488f140f60e897ea6b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x0d4de01fe5360c38b4ad2b0822a12722958428f5138a7ff47c1720eb6fa52bba\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x2ccf9d2313a313d41a791505f2b5abfdc62191b5d4334f7f7a82691c088a1c87\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonce;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `spender` and `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `spender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(spender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(spender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonce[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(spender, recipient, amount);\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[spender] = spenderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(spender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x1e1bf4ec5c9d6fe70f6f834316482aeff3f122ff4ffaa7178099e7ae71a0b16d\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonce(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xdac9a5086c19a7128b505a7be1ab0ac1aa314f6989cb88d2417e9d7383f89fa9\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/token/TBTC.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\\n constructor() ERC20WithPermit(\\\"tBTC v2\\\", \\\"tBTC\\\") {}\\n}\\n\",\"keccak256\":\"0x4542aaa48f7682005b815253768b6433c811daff5d2727db256d5f1879f5ccbf\",\"license\":\"GPL-3.0-only\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b50604051806040016040528060078152602001663a212a21903b1960c91b815250604051806040016040528060048152602001637442544360e01b8152506200006962000063620000a160201b60201c565b620000a5565b60056200007783826200024b565b5060066200008682826200024b565b504660805262000095620000f5565b60a05250620003959050565b3390565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600560405162000129919062000317565b60408051918290038220828201825260018352603160f81b6020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b634e487b7160e01b600052604160045260246000fd5b600181811c90821680620001d157607f821691505b602082108103620001f257634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200024657600081815260208120601f850160051c81016020861015620002215750805b601f850160051c820191505b8181101562000242578281556001016200022d565b5050505b505050565b81516001600160401b03811115620002675762000267620001a6565b6200027f81620002788454620001bc565b84620001f8565b602080601f831160018114620002b757600084156200029e5750858301515b600019600386901b1c1916600185901b17855562000242565b600085815260208120601f198616915b82811015620002e857888601518255948401946001909101908401620002c7565b5085821015620003075787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008083546200032781620001bc565b60018281168015620003425760018114620003585762000389565b60ff198416875282151583028701945062000389565b8760005260208060002060005b85811015620003805781548a82015290840190820162000365565b50505082870194505b50929695505050505050565b60805160a051611b92620003c96000396000818161034901526105fa0152600081816102d901526105d20152611b926000f3fe608060405234801561001057600080fd5b50600436106101985760003560e01c8063715018a6116100e3578063b4f94b2e1161008c578063dd62ed3e11610066578063dd62ed3e14610391578063f2fde38b146103bc578063fc4e51f6146103cf57600080fd5b8063b4f94b2e14610344578063cae9ca511461036b578063d505accf1461037e57600080fd5b80638da5cb5b116100bd5780638da5cb5b1461030e57806395d89b4114610329578063a9059cbb1461033157600080fd5b8063715018a6146102cc578063771da5c5146102d457806379cc6790146102fb57600080fd5b8063313ce5671161014557806342966c681161011f57806342966c681461027957806370a082311461028c57806370ae92d2146102ac57600080fd5b8063313ce567146102445780633644e5151461025e57806340c10f191461026657600080fd5b806318160ddd1161017657806318160ddd146101f357806323b872dd1461020a57806330adf81f1461021d57600080fd5b806306fdde031461019d578063095ea7b3146101bb5780631171bda9146101de575b600080fd5b6101a56103e2565b6040516101b29190611663565b60405180910390f35b6101ce6101c936600461168b565b610470565b60405190151581526020016101b2565b6101f16101ec3660046116b7565b610487565b005b6101fc60045481565b6040519081526020016101b2565b6101ce6102183660046116b7565b6104ff565b6101fc7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b61024c601281565b60405160ff90911681526020016101b2565b6101fc6105ce565b6101f161027436600461168b565b610629565b6101f16102873660046116f8565b610762565b6101fc61029a366004611711565b60016020526000908152604090205481565b6101fc6102ba366004611711565b60036020526000908152604090205481565b6101f161076f565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101f161030936600461168b565b6107d5565b6000546040516001600160a01b0390911681526020016101b2565b6101a561086b565b6101ce61033f36600461168b565b610878565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101ce610379366004611744565b610885565b6101f161038c366004611811565b610926565b6101fc61039f366004611888565b600260209081526000928352604080842090915290825290205481565b6101f16103ca366004611711565b610c36565b6101f16103dd3660046118c1565b610d15565b600580546103ef90611960565b80601f016020809104026020016040519081016040528092919081815260200182805461041b90611960565b80156104685780601f1061043d57610100808354040283529160200191610468565b820191906000526020600020905b81548152906001019060200180831161044b57829003601f168201915b505050505081565b600061047d338484610dea565b5060015b92915050565b6000546001600160a01b031633146104e65760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6104fa6001600160a01b0384168383610ef8565b505050565b6001600160a01b038316600090815260026020908152604080832033845290915281205460001981146105b657828110156105a25760405162461bcd60e51b815260206004820152602160248201527f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084016104dd565b6105b685336105b186856119b0565b610dea565b6105c1858585610f78565b60019150505b9392505050565b60007f0000000000000000000000000000000000000000000000000000000000000000460361061c57507f000000000000000000000000000000000000000000000000000000000000000090565b61062461117f565b905090565b6000546001600160a01b031633146106835760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6001600160a01b0382166106d95760405162461bcd60e51b815260206004820152601860248201527f4d696e7420746f20746865207a65726f2061646472657373000000000000000060448201526064016104dd565b80600460008282546106eb91906119c3565b90915550506001600160a01b038216600090815260016020526040812080548392906107189084906119c3565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b61076c338261124a565b50565b6000546001600160a01b031633146107c95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6107d3600061132e565b565b6001600160a01b0382166000908152600260209081526040808320338452909152902054600019811461086157818110156108525760405162461bcd60e51b815260206004820152601d60248201527f4275726e20616d6f756e74206578636565647320616c6c6f77616e636500000060448201526064016104dd565b61086183336105b185856119b0565b6104fa838361124a565b600680546103ef90611960565b600061047d338484610f78565b60006108918484610470565b1561091c576040517f8f4ffcb10000000000000000000000000000000000000000000000000000000081526001600160a01b03851690638f4ffcb1906108e19033908790309088906004016119d6565b600060405180830381600087803b1580156108fb57600080fd5b505af115801561090f573d6000803e3d6000fd5b50505050600190506105c7565b5060009392505050565b428410156109765760405162461bcd60e51b815260206004820152601260248201527f5065726d697373696f6e2065787069726564000000000000000000000000000060448201526064016104dd565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08111156109e65760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202773272076616c7565000000000060448201526064016104dd565b8260ff16601b14806109fb57508260ff16601c145b610a475760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202776272076616c7565000000000060448201526064016104dd565b6000610a516105ce565b6001600160a01b038916600090815260036020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9928c928c928c92909190610a9f83611a12565b909155506040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810187905260e00160405160208183030381529060405280519060200120604051602001610b339291907f190100000000000000000000000000000000000000000000000000000000000081526002810192909252602282015260420190565b60408051601f198184030181528282528051602091820120600080855291840180845281905260ff88169284019290925260608301869052608083018590529092509060019060a0016020604051602081039080840390855afa158015610b9e573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610bd45750886001600160a01b0316816001600160a01b0316145b610c205760405162461bcd60e51b815260206004820152601160248201527f496e76616c6964207369676e617475726500000000000000000000000000000060448201526064016104dd565b610c2b898989610dea565b505050505050505050565b6000546001600160a01b03163314610c905760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6001600160a01b038116610d0c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016104dd565b61076c8161132e565b6000546001600160a01b03163314610d6f5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde90610dbc9030908890889088908890600401611a2b565b600060405180830381600087803b158015610dd657600080fd5b505af1158015610c2b573d6000803e3d6000fd5b6001600160a01b038316610e405760405162461bcd60e51b815260206004820152601d60248201527f417070726f76652066726f6d20746865207a65726f206164647265737300000060448201526064016104dd565b6001600160a01b038216610e965760405162461bcd60e51b815260206004820152601b60248201527f417070726f766520746f20746865207a65726f2061646472657373000000000060448201526064016104dd565b6001600160a01b0383811660008181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790526104fa908490611396565b6001600160a01b038316610fce5760405162461bcd60e51b815260206004820152601e60248201527f5472616e736665722066726f6d20746865207a65726f2061646472657373000060448201526064016104dd565b6001600160a01b0382166110245760405162461bcd60e51b815260206004820152601c60248201527f5472616e7366657220746f20746865207a65726f20616464726573730000000060448201526064016104dd565b306001600160a01b0383160361107c5760405162461bcd60e51b815260206004820152601d60248201527f5472616e7366657220746f2074686520746f6b656e206164647265737300000060448201526064016104dd565b6001600160a01b038316600090815260016020526040902054818110156110e55760405162461bcd60e51b815260206004820152601f60248201527f5472616e7366657220616d6f756e7420657863656564732062616c616e63650060448201526064016104dd565b6110ef82826119b0565b6001600160a01b0380861660009081526001602052604080822093909355908516815290812080548492906111259084906119c3565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161117191815260200190565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60056040516111b19190611a7f565b604080519182900382208282018252600183527f31000000000000000000000000000000000000000000000000000000000000006020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6001600160a01b038216600090815260016020526040902054818110156112b35760405162461bcd60e51b815260206004820152601b60248201527f4275726e20616d6f756e7420657863656564732062616c616e6365000000000060448201526064016104dd565b6112bd82826119b0565b6001600160a01b038416600090815260016020526040812091909155600480548492906112eb9084906119b0565b90915550506040518281526000906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610eeb565b600080546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006113eb826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661147b9092919063ffffffff16565b8051909150156104fa57808060200190518101906114099190611b1e565b6104fa5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016104dd565b606061148a8484600085611492565b949350505050565b60608247101561150a5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016104dd565b6001600160a01b0385163b6115615760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016104dd565b600080866001600160a01b0316858760405161157d9190611b40565b60006040518083038185875af1925050503d80600081146115ba576040519150601f19603f3d011682016040523d82523d6000602084013e6115bf565b606091505b50915091506115cf8282866115da565b979650505050505050565b606083156115e95750816105c7565b8251156115f95782518084602001fd5b8160405162461bcd60e51b81526004016104dd9190611663565b60005b8381101561162e578181015183820152602001611616565b50506000910152565b6000815180845261164f816020860160208601611613565b601f01601f19169290920160200192915050565b6020815260006105c76020830184611637565b6001600160a01b038116811461076c57600080fd5b6000806040838503121561169e57600080fd5b82356116a981611676565b946020939093013593505050565b6000806000606084860312156116cc57600080fd5b83356116d781611676565b925060208401356116e781611676565b929592945050506040919091013590565b60006020828403121561170a57600080fd5b5035919050565b60006020828403121561172357600080fd5b81356105c781611676565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561175957600080fd5b833561176481611676565b925060208401359150604084013567ffffffffffffffff8082111561178857600080fd5b818601915086601f83011261179c57600080fd5b8135818111156117ae576117ae61172e565b604051601f8201601f19908116603f011681019083821181831017156117d6576117d661172e565b816040528281528960208487010111156117ef57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600080600080600080600060e0888a03121561182c57600080fd5b873561183781611676565b9650602088013561184781611676565b95506040880135945060608801359350608088013560ff8116811461186b57600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561189b57600080fd5b82356118a681611676565b915060208301356118b681611676565b809150509250929050565b6000806000806000608086880312156118d957600080fd5b85356118e481611676565b945060208601356118f481611676565b935060408601359250606086013567ffffffffffffffff8082111561191857600080fd5b818801915088601f83011261192c57600080fd5b81358181111561193b57600080fd5b89602082850101111561194d57600080fd5b9699959850939650602001949392505050565b600181811c9082168061197457607f821691505b60208210810361199457634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156104815761048161199a565b808201808211156104815761048161199a565b60006001600160a01b03808716835285602084015280851660408401525060806060830152611a086080830184611637565b9695505050505050565b600060018201611a2457611a2461199a565b5060010190565b60006001600160a01b03808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b600080835481600182811c915080831680611a9b57607f831692505b60208084108203611aba57634e487b7160e01b86526022600452602486fd5b818015611ace5760018114611ae357611b10565b60ff1986168952841515850289019650611b10565b60008a81526020902060005b86811015611b085781548b820152908501908301611aef565b505084890196505b509498975050505050505050565b600060208284031215611b3057600080fd5b815180151581146105c757600080fd5b60008251611b52818460208701611613565b919091019291505056fea264697066735822122014ba9a6dc40cd5a66877a573c7b8d56d9c3914d7f5b45a829d543c5233bd296664736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101985760003560e01c8063715018a6116100e3578063b4f94b2e1161008c578063dd62ed3e11610066578063dd62ed3e14610391578063f2fde38b146103bc578063fc4e51f6146103cf57600080fd5b8063b4f94b2e14610344578063cae9ca511461036b578063d505accf1461037e57600080fd5b80638da5cb5b116100bd5780638da5cb5b1461030e57806395d89b4114610329578063a9059cbb1461033157600080fd5b8063715018a6146102cc578063771da5c5146102d457806379cc6790146102fb57600080fd5b8063313ce5671161014557806342966c681161011f57806342966c681461027957806370a082311461028c57806370ae92d2146102ac57600080fd5b8063313ce567146102445780633644e5151461025e57806340c10f191461026657600080fd5b806318160ddd1161017657806318160ddd146101f357806323b872dd1461020a57806330adf81f1461021d57600080fd5b806306fdde031461019d578063095ea7b3146101bb5780631171bda9146101de575b600080fd5b6101a56103e2565b6040516101b29190611663565b60405180910390f35b6101ce6101c936600461168b565b610470565b60405190151581526020016101b2565b6101f16101ec3660046116b7565b610487565b005b6101fc60045481565b6040519081526020016101b2565b6101ce6102183660046116b7565b6104ff565b6101fc7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b61024c601281565b60405160ff90911681526020016101b2565b6101fc6105ce565b6101f161027436600461168b565b610629565b6101f16102873660046116f8565b610762565b6101fc61029a366004611711565b60016020526000908152604090205481565b6101fc6102ba366004611711565b60036020526000908152604090205481565b6101f161076f565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101f161030936600461168b565b6107d5565b6000546040516001600160a01b0390911681526020016101b2565b6101a561086b565b6101ce61033f36600461168b565b610878565b6101fc7f000000000000000000000000000000000000000000000000000000000000000081565b6101ce610379366004611744565b610885565b6101f161038c366004611811565b610926565b6101fc61039f366004611888565b600260209081526000928352604080842090915290825290205481565b6101f16103ca366004611711565b610c36565b6101f16103dd3660046118c1565b610d15565b600580546103ef90611960565b80601f016020809104026020016040519081016040528092919081815260200182805461041b90611960565b80156104685780601f1061043d57610100808354040283529160200191610468565b820191906000526020600020905b81548152906001019060200180831161044b57829003601f168201915b505050505081565b600061047d338484610dea565b5060015b92915050565b6000546001600160a01b031633146104e65760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064015b60405180910390fd5b6104fa6001600160a01b0384168383610ef8565b505050565b6001600160a01b038316600090815260026020908152604080832033845290915281205460001981146105b657828110156105a25760405162461bcd60e51b815260206004820152602160248201527f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084016104dd565b6105b685336105b186856119b0565b610dea565b6105c1858585610f78565b60019150505b9392505050565b60007f0000000000000000000000000000000000000000000000000000000000000000460361061c57507f000000000000000000000000000000000000000000000000000000000000000090565b61062461117f565b905090565b6000546001600160a01b031633146106835760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6001600160a01b0382166106d95760405162461bcd60e51b815260206004820152601860248201527f4d696e7420746f20746865207a65726f2061646472657373000000000000000060448201526064016104dd565b80600460008282546106eb91906119c3565b90915550506001600160a01b038216600090815260016020526040812080548392906107189084906119c3565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b61076c338261124a565b50565b6000546001600160a01b031633146107c95760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6107d3600061132e565b565b6001600160a01b0382166000908152600260209081526040808320338452909152902054600019811461086157818110156108525760405162461bcd60e51b815260206004820152601d60248201527f4275726e20616d6f756e74206578636565647320616c6c6f77616e636500000060448201526064016104dd565b61086183336105b185856119b0565b6104fa838361124a565b600680546103ef90611960565b600061047d338484610f78565b60006108918484610470565b1561091c576040517f8f4ffcb10000000000000000000000000000000000000000000000000000000081526001600160a01b03851690638f4ffcb1906108e19033908790309088906004016119d6565b600060405180830381600087803b1580156108fb57600080fd5b505af115801561090f573d6000803e3d6000fd5b50505050600190506105c7565b5060009392505050565b428410156109765760405162461bcd60e51b815260206004820152601260248201527f5065726d697373696f6e2065787069726564000000000000000000000000000060448201526064016104dd565b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08111156109e65760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202773272076616c7565000000000060448201526064016104dd565b8260ff16601b14806109fb57508260ff16601c145b610a475760405162461bcd60e51b815260206004820152601b60248201527f496e76616c6964207369676e6174757265202776272076616c7565000000000060448201526064016104dd565b6000610a516105ce565b6001600160a01b038916600090815260036020526040812080547f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9928c928c928c92909190610a9f83611a12565b909155506040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810187905260e00160405160208183030381529060405280519060200120604051602001610b339291907f190100000000000000000000000000000000000000000000000000000000000081526002810192909252602282015260420190565b60408051601f198184030181528282528051602091820120600080855291840180845281905260ff88169284019290925260608301869052608083018590529092509060019060a0016020604051602081039080840390855afa158015610b9e573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811615801590610bd45750886001600160a01b0316816001600160a01b0316145b610c205760405162461bcd60e51b815260206004820152601160248201527f496e76616c6964207369676e617475726500000000000000000000000000000060448201526064016104dd565b610c2b898989610dea565b505050505050505050565b6000546001600160a01b03163314610c905760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6001600160a01b038116610d0c5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016104dd565b61076c8161132e565b6000546001600160a01b03163314610d6f5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016104dd565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde90610dbc9030908890889088908890600401611a2b565b600060405180830381600087803b158015610dd657600080fd5b505af1158015610c2b573d6000803e3d6000fd5b6001600160a01b038316610e405760405162461bcd60e51b815260206004820152601d60248201527f417070726f76652066726f6d20746865207a65726f206164647265737300000060448201526064016104dd565b6001600160a01b038216610e965760405162461bcd60e51b815260206004820152601b60248201527f417070726f766520746f20746865207a65726f2061646472657373000000000060448201526064016104dd565b6001600160a01b0383811660008181526002602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb000000000000000000000000000000000000000000000000000000001790526104fa908490611396565b6001600160a01b038316610fce5760405162461bcd60e51b815260206004820152601e60248201527f5472616e736665722066726f6d20746865207a65726f2061646472657373000060448201526064016104dd565b6001600160a01b0382166110245760405162461bcd60e51b815260206004820152601c60248201527f5472616e7366657220746f20746865207a65726f20616464726573730000000060448201526064016104dd565b306001600160a01b0383160361107c5760405162461bcd60e51b815260206004820152601d60248201527f5472616e7366657220746f2074686520746f6b656e206164647265737300000060448201526064016104dd565b6001600160a01b038316600090815260016020526040902054818110156110e55760405162461bcd60e51b815260206004820152601f60248201527f5472616e7366657220616d6f756e7420657863656564732062616c616e63650060448201526064016104dd565b6110ef82826119b0565b6001600160a01b0380861660009081526001602052604080822093909355908516815290812080548492906111259084906119c3565b92505081905550826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161117191815260200190565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60056040516111b19190611a7f565b604080519182900382208282018252600183527f31000000000000000000000000000000000000000000000000000000000000006020938401528151928301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b6001600160a01b038216600090815260016020526040902054818110156112b35760405162461bcd60e51b815260206004820152601b60248201527f4275726e20616d6f756e7420657863656564732062616c616e6365000000000060448201526064016104dd565b6112bd82826119b0565b6001600160a01b038416600090815260016020526040812091909155600480548492906112eb9084906119b0565b90915550506040518281526000906001600160a01b038516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610eeb565b600080546001600160a01b038381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006113eb826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661147b9092919063ffffffff16565b8051909150156104fa57808060200190518101906114099190611b1e565b6104fa5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016104dd565b606061148a8484600085611492565b949350505050565b60608247101561150a5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016104dd565b6001600160a01b0385163b6115615760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016104dd565b600080866001600160a01b0316858760405161157d9190611b40565b60006040518083038185875af1925050503d80600081146115ba576040519150601f19603f3d011682016040523d82523d6000602084013e6115bf565b606091505b50915091506115cf8282866115da565b979650505050505050565b606083156115e95750816105c7565b8251156115f95782518084602001fd5b8160405162461bcd60e51b81526004016104dd9190611663565b60005b8381101561162e578181015183820152602001611616565b50506000910152565b6000815180845261164f816020860160208601611613565b601f01601f19169290920160200192915050565b6020815260006105c76020830184611637565b6001600160a01b038116811461076c57600080fd5b6000806040838503121561169e57600080fd5b82356116a981611676565b946020939093013593505050565b6000806000606084860312156116cc57600080fd5b83356116d781611676565b925060208401356116e781611676565b929592945050506040919091013590565b60006020828403121561170a57600080fd5b5035919050565b60006020828403121561172357600080fd5b81356105c781611676565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561175957600080fd5b833561176481611676565b925060208401359150604084013567ffffffffffffffff8082111561178857600080fd5b818601915086601f83011261179c57600080fd5b8135818111156117ae576117ae61172e565b604051601f8201601f19908116603f011681019083821181831017156117d6576117d661172e565b816040528281528960208487010111156117ef57600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b600080600080600080600060e0888a03121561182c57600080fd5b873561183781611676565b9650602088013561184781611676565b95506040880135945060608801359350608088013560ff8116811461186b57600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561189b57600080fd5b82356118a681611676565b915060208301356118b681611676565b809150509250929050565b6000806000806000608086880312156118d957600080fd5b85356118e481611676565b945060208601356118f481611676565b935060408601359250606086013567ffffffffffffffff8082111561191857600080fd5b818801915088601f83011261192c57600080fd5b81358181111561193b57600080fd5b89602082850101111561194d57600080fd5b9699959850939650602001949392505050565b600181811c9082168061197457607f821691505b60208210810361199457634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b818103818111156104815761048161199a565b808201808211156104815761048161199a565b60006001600160a01b03808716835285602084015280851660408401525060806060830152611a086080830184611637565b9695505050505050565b600060018201611a2457611a2461199a565b5060010190565b60006001600160a01b03808816835280871660208401525084604083015260806060830152826080830152828460a0840137600060a0848401015260a0601f19601f85011683010190509695505050505050565b600080835481600182811c915080831680611a9b57607f831692505b60208084108203611aba57634e487b7160e01b86526022600452602486fd5b818015611ace5760018114611ae357611b10565b60ff1986168952841515850289019650611b10565b60008a81526020902060005b86811015611b085781548b820152908501908301611aef565b505084890196505b509498975050505050505050565b600060208284031215611b3057600080fd5b815180151581146105c757600080fd5b60008251611b52818460208701611613565b919091019291505056fea264697066735822122014ba9a6dc40cd5a66877a573c7b8d56d9c3914d7f5b45a829d543c5233bd296664736f6c63430008110033", + "devdoc": { + "kind": "dev", + "methods": { + "approve(address,uint256)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729", + "returns": { + "_0": "True if the operation succeeded." + } + }, + "approveAndCall(address,uint256,bytes)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.", + "returns": { + "_0": "True if both approval and `receiveApproval` calls succeeded." + } + }, + "burn(uint256)": { + "details": "Requirements: - the caller must have a balance of at least `amount`." + }, + "burnFrom(address,uint256)": { + "details": "Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`." + }, + "mint(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address." + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "details": "The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "transfer(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferFrom(address,address,uint256)": { + "details": "Requirements: - `spender` and `recipient` cannot be the zero address, - `spender` must have a balance of at least `amount`, - the caller must have allowance for `spender`'s tokens of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "DOMAIN_SEPARATOR()": { + "notice": "Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function." + }, + "PERMIT_TYPEHASH()": { + "notice": "Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function." + }, + "allowance(address,address)": { + "notice": "The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default." + }, + "approve(address,uint256)": { + "notice": "Sets `amount` as the allowance of `spender` over the caller's tokens." + }, + "approveAndCall(address,uint256,bytes)": { + "notice": "Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted." + }, + "balanceOf(address)": { + "notice": "The amount of tokens owned by the given account." + }, + "burn(uint256)": { + "notice": "Destroys `amount` tokens from the caller." + }, + "burnFrom(address,uint256)": { + "notice": "Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + }, + "decimals()": { + "notice": "The decimals places of the token." + }, + "mint(address,uint256)": { + "notice": "Creates `amount` tokens and assigns them to `account`, increasing the total supply." + }, + "name()": { + "notice": "The name of the token." + }, + "nonce(address)": { + "notice": "Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "notice": "EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction." + }, + "symbol()": { + "notice": "The symbol of the token." + }, + "totalSupply()": { + "notice": "The amount of tokens in existence." + }, + "transfer(address,uint256)": { + "notice": "Moves `amount` tokens from the caller's account to `recipient`." + }, + "transferFrom(address,address,uint256)": { + "notice": "Moves `amount` tokens from `spender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 13249, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 16610, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "balanceOf", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 16618, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "allowance", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 16624, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "nonce", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 16639, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "totalSupply", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 16643, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "name", + "offset": 0, + "slot": "5", + "type": "t_string_storage" + }, + { + "astId": 16647, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "symbol", + "offset": 0, + "slot": "6", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} diff --git a/typescript/src/lib/ethereum/artifacts/goerli/TBTCVault.json b/typescript/src/lib/ethereum/artifacts/goerli/TBTCVault.json new file mode 100644 index 000000000..c55369319 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/goerli/TBTCVault.json @@ -0,0 +1,1606 @@ +{ + "address": "0x65eB0562FCe858f8328858c76E689aBedB78621F", + "abi": [ + { + "inputs": [ + { + "internalType": "contract Bank", + "name": "_bank", + "type": "address" + }, + { + "internalType": "contract TBTC", + "name": "_tbtcToken", + "type": "address" + }, + { + "internalType": "contract Bridge", + "name": "_bridge", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "GuardianAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "GuardianRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Minted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "MinterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guardian", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + } + ], + "name": "OptimisticMintingCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "optimisticMintingDebt", + "type": "uint256" + } + ], + "name": "OptimisticMintingDebtRepaid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingDelay", + "type": "uint32" + } + ], + "name": "OptimisticMintingDelayUpdateStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingDelay", + "type": "uint32" + } + ], + "name": "OptimisticMintingDelayUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingFeeDivisor", + "type": "uint32" + } + ], + "name": "OptimisticMintingFeeUpdateStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingFeeDivisor", + "type": "uint32" + } + ], + "name": "OptimisticMintingFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "optimisticMintingDebt", + "type": "uint256" + } + ], + "name": "OptimisticMintingFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "OptimisticMintingPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "OptimisticMintingRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "OptimisticMintingUnpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Unminted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newVault", + "type": "address" + } + ], + "name": "UpgradeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "UpgradeInitiated", + "type": "event" + }, + { + "inputs": [], + "name": "GOVERNANCE_DELAY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SATOSHI_MULTIPLIER", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "addGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "amountToSatoshis", + "outputs": [ + { + "internalType": "uint256", + "name": "convertibleAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "remainder", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "satoshis", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bank", + "outputs": [ + { + "internalType": "contract Bank", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_newOptimisticMintingDelay", + "type": "uint32" + } + ], + "name": "beginOptimisticMintingDelayUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_newOptimisticMintingFeeDivisor", + "type": "uint32" + } + ], + "name": "beginOptimisticMintingFeeUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "bridge", + "outputs": [ + { + "internalType": "contract Bridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "calculateDepositKey", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "cancelOptimisticMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "finalizeOptimisticMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeOptimisticMintingDelayUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeOptimisticMintingFeeUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getMinters", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newVault", + "type": "address" + } + ], + "name": "initiateUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isGuardian", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isOptimisticMintingPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "minters", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newOptimisticMintingDelay", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newOptimisticMintingFeeDivisor", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "optimisticMintingDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingDelay", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingDelayUpdateInitiatedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingFeeDivisor", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingFeeUpdateInitiatedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "optimisticMintingRequests", + "outputs": [ + { + "internalType": "uint64", + "name": "requestedAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "finalizedAt", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauseOptimisticMinting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "receiveApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "satoshis", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "receiveBalanceApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "depositors", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "depositedSatoshiAmounts", + "type": "uint256[]" + } + ], + "name": "receiveBalanceIncrease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20FromToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721FromToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "removeGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "removeMinter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "requestOptimisticMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "tbtcToken", + "outputs": [ + { + "internalType": "contract TBTC", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unmint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "redemptionData", + "type": "bytes" + } + ], + "name": "unmintAndRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseOptimisticMinting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeInitiatedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x8bcb0534a00a776fc21229184663ac39368db772ee2ba692dcf889265a7e8370", + "receipt": { + "to": null, + "from": "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", + "contractAddress": "0x65eB0562FCe858f8328858c76E689aBedB78621F", + "transactionIndex": 15, + "gasUsed": "3340784", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000200000001000000000000000000000000000000000000020000000000000000000800000000000000000000040000000000400000000200000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x487502d84725255efc9d9892c338cc0916437ebe118c95ebf566864efcce2d5f", + "transactionHash": "0x8bcb0534a00a776fc21229184663ac39368db772ee2ba692dcf889265a7e8370", + "logs": [ + { + "transactionIndex": 15, + "blockNumber": 8364960, + "transactionHash": "0x8bcb0534a00a776fc21229184663ac39368db772ee2ba692dcf889265a7e8370", + "address": "0x65eB0562FCe858f8328858c76E689aBedB78621F", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000068ad60cc5e8f3b7cc53beab321cf0e6036962dbc" + ], + "data": "0x", + "logIndex": 19, + "blockHash": "0x487502d84725255efc9d9892c338cc0916437ebe118c95ebf566864efcce2d5f" + } + ], + "blockNumber": 8364960, + "cumulativeGasUsed": "7425600", + "status": 1, + "byzantium": true + }, + "args": [ + "0xd5040917dA4f297Fa20a2F3A4fF91416C08bfB39", + "0x679874fBE6D4E7Cc54A59e315FF1eB266686a937", + "0x0Cad3257C4B7ec6de1f6926Fbf5714255a6632c3" + ], + "numDeployments": 1, + "solcInputHash": "f862bd134ddc06352fa5be0644481600", + "metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract Bank\",\"name\":\"_bank\",\"type\":\"address\"},{\"internalType\":\"contract TBTC\",\"name\":\"_tbtcToken\",\"type\":\"address\"},{\"internalType\":\"contract Bridge\",\"name\":\"_bridge\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"GuardianAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"GuardianRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Minted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"MinterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"MinterRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositKey\",\"type\":\"uint256\"}],\"name\":\"OptimisticMintingCancelled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticMintingDebt\",\"type\":\"uint256\"}],\"name\":\"OptimisticMintingDebtRepaid\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingDelay\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingDelayUpdateStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingDelay\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingDelayUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingFeeDivisor\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingFeeUpdateStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingFeeDivisor\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingFeeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositKey\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticMintingDebt\",\"type\":\"uint256\"}],\"name\":\"OptimisticMintingFinalized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"OptimisticMintingPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositKey\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"OptimisticMintingUnpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unminted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newVault\",\"type\":\"address\"}],\"name\":\"UpgradeFinalized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newVault\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"UpgradeInitiated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"GOVERNANCE_DELAY\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SATOSHI_MULTIPLIER\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"addGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"addMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"amountToSatoshis\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"convertibleAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"remainder\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"satoshis\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bank\",\"outputs\":[{\"internalType\":\"contract Bank\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_newOptimisticMintingDelay\",\"type\":\"uint32\"}],\"name\":\"beginOptimisticMintingDelayUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_newOptimisticMintingFeeDivisor\",\"type\":\"uint32\"}],\"name\":\"beginOptimisticMintingFeeUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bridge\",\"outputs\":[{\"internalType\":\"contract Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"calculateDepositKey\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"cancelOptimisticMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"finalizeOptimisticMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"finalizeOptimisticMintingDelayUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"finalizeOptimisticMintingFeeUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"finalizeUpgrade\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getMinters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newVault\",\"type\":\"address\"}],\"name\":\"initiateUpgrade\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isGuardian\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isOptimisticMintingPaused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"minters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newOptimisticMintingDelay\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newOptimisticMintingFeeDivisor\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newVault\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"optimisticMintingDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingDelay\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingDelayUpdateInitiatedTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingFeeDivisor\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingFeeUpdateInitiatedTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"optimisticMintingRequests\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"requestedAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"finalizedAt\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauseOptimisticMinting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"receiveApproval\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"satoshis\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"receiveBalanceApproval\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"depositors\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"depositedSatoshiAmounts\",\"type\":\"uint256[]\"}],\"name\":\"receiveBalanceIncrease\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20FromToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721FromToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"removeGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"removeMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"requestOptimisticMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tbtcToken\",\"outputs\":[{\"internalType\":\"contract TBTC\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unmint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"redemptionData\",\"type\":\"bytes\"}],\"name\":\"unmintAndRedeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpauseOptimisticMinting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"upgradeInitiatedTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"TBTC Vault is the owner of TBTC token contract and is the only contract minting the token.\",\"kind\":\"dev\",\"methods\":{\"amountToSatoshis(uint256)\":{\"returns\":{\"convertibleAmount\":\"Amount of TBTC to be minted/unminted.\",\"remainder\":\"Not convertible remainder if amount is not divisible by SATOSHI_MULTIPLIER.\",\"satoshis\":\"Amount in satoshis - the Bank balance to be transferred for the given mint/unmint\"}},\"beginOptimisticMintingFeeUpdate(uint32)\":{\"details\":\"See the documentation for optimisticMintingFeeDivisor.\"},\"cancelOptimisticMint(bytes32,uint32)\":{\"details\":\"Guardians must validate the following conditions for every deposit for which the optimistic minting was requested: - The deposit happened on Bitcoin side and it has enough confirmations. - The optimistic minting has been requested early enough so that the wallet has enough time to sweep the deposit. - The wallet is an active one and it does perform sweeps or it will perform sweeps once the sweeps are activated.\"},\"initiateUpgrade(address)\":{\"params\":{\"_newVault\":\"The new vault address.\"}},\"mint(uint256)\":{\"details\":\"TBTC Vault must have an allowance for caller's balance in the Bank for at least `amount / SATOSHI_MULTIPLIER`.\",\"params\":{\"amount\":\"Amount of TBTC to mint.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"receiveApproval(address,uint256,address,bytes)\":{\"details\":\"This function is doing the same as `unmint` or `unmintAndRedeem` (depending on `extraData` parameter) but it allows to execute unminting without a separate approval transaction. The function can be called only via `approveAndCall` of TBTC token.\",\"params\":{\"amount\":\"Amount of TBTC to unmint.\",\"extraData\":\"Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function. If empty, `receiveApproval` is not requesting a redemption of Bank balance but is instead performing just TBTC unminting to a Bank balance.\",\"from\":\"TBTC token holder executing unminting.\",\"token\":\"TBTC token address.\"}},\"receiveBalanceApproval(address,uint256,bytes)\":{\"details\":\"Can only be called by the Bank via `approveBalanceAndCall`.\",\"params\":{\"owner\":\"The owner who approved their Bank balance.\",\"satoshis\":\"Amount of satoshis used to mint TBTC.\"}},\"receiveBalanceIncrease(address[],uint256[])\":{\"details\":\"Fails if `depositors` array is empty. Expects the length of `depositors` and `depositedSatoshiAmounts` is the same.\"},\"recoverERC20(address,address,uint256)\":{\"params\":{\"amount\":\"Recovered amount.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC20 token contract.\"}},\"recoverERC20FromToken(address,address,uint256)\":{\"params\":{\"amount\":\"Recovered amount.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC20 token contract.\"}},\"recoverERC721(address,address,uint256,bytes)\":{\"params\":{\"data\":\"Additional data.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC721 token contract.\",\"tokenId\":\"Identifier of the recovered token.\"}},\"recoverERC721FromToken(address,address,uint256,bytes)\":{\"params\":{\"data\":\"Additional data.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC721 token contract.\",\"tokenId\":\"Identifier of the recovered token.\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"requestOptimisticMint(bytes32,uint32)\":{\"details\":\"The deposit done on the Bitcoin side must be revealed early enough to the Bridge on Ethereum to pass the Bridge's validation. The validation passes successfully only if the deposit reveal is done respectively earlier than the moment when the deposit refund locktime is reached, i.e. the deposit becomes refundable. It may happen that the wallet does not sweep a revealed deposit and one of the Minters requests an optimistic mint for that deposit just before the locktime is reached. Guardians must cancel optimistic minting for this deposit because the wallet will not be able to sweep it. The on-chain optimistic minting code does not perform any validation for gas efficiency: it would have to perform the same validation as `validateDepositRefundLocktime` and expect the entire `DepositRevealInfo` to be passed to assemble the expected script hash on-chain. Guardians must validate if the deposit happened on Bitcoin, that the script hash has the expected format, and that the wallet is an active one so they can also validate the time left for the refund.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"unmint(uint256)\":{\"details\":\"Caller must have at least `amount` of TBTC approved to TBTC Vault.\",\"params\":{\"amount\":\"Amount of TBTC to unmint.\"}},\"unmintAndRedeem(uint256,bytes)\":{\"details\":\"Caller must have at least `amount` of TBTC approved to TBTC Vault.\",\"params\":{\"amount\":\"Amount of TBTC to unmint and request to redeem in Bridge.\",\"redemptionData\":\"Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function.\"}}},\"title\":\"TBTC application vault\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"GOVERNANCE_DELAY()\":{\"notice\":\"The time delay that needs to pass between initializing and finalizing the upgrade of governable parameters.\"},\"SATOSHI_MULTIPLIER()\":{\"notice\":\"Multiplier to convert satoshi to TBTC token units.\"},\"addGuardian(address)\":{\"notice\":\"Adds the address to the Guardian set.\"},\"addMinter(address)\":{\"notice\":\"Adds the address to the Minter list.\"},\"amountToSatoshis(uint256)\":{\"notice\":\"Returns the amount of TBTC to be minted/unminted, the remainder, and the Bank balance to be transferred for the given mint/unmint. Note that if the `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account when minting or unminting.\"},\"beginOptimisticMintingDelayUpdate(uint32)\":{\"notice\":\"Begins the process of updating optimistic minting delay.\"},\"beginOptimisticMintingFeeUpdate(uint32)\":{\"notice\":\"Begins the process of updating optimistic minting fee. The fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2% of each deposit, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`.\"},\"calculateDepositKey(bytes32,uint32)\":{\"notice\":\"Calculates deposit key the same way as the Bridge contract. The deposit key is computed as `keccak256(fundingTxHash | fundingOutputIndex)`.\"},\"cancelOptimisticMint(bytes32,uint32)\":{\"notice\":\"Allows a Guardian to cancel optimistic minting request. The following conditions must be met: - The optimistic minting request for the given deposit exists. - The optimistic minting request for the given deposit has not been finalized yet. Optimistic minting request is removed. It is possible to request optimistic minting again for the same deposit later.\"},\"finalizeOptimisticMint(bytes32,uint32)\":{\"notice\":\"Allows a Minter to finalize previously requested optimistic minting. The following conditions must be met: - The optimistic minting has been requested for the given deposit. - The deposit has not been swept yet. - At least `optimisticMintingDelay` passed since the optimistic minting was requested for the given deposit. - The optimistic minting has not been finalized earlier for the given deposit. - The optimistic minting request for the given deposit has not been canceled by a Guardian. - The optimistic minting is not paused. This function mints TBTC and increases `optimisticMintingDebt` for the given depositor. The optimistic minting request is marked as finalized.\"},\"finalizeOptimisticMintingDelayUpdate()\":{\"notice\":\"Finalizes the update process of the optimistic minting delay.\"},\"finalizeOptimisticMintingFeeUpdate()\":{\"notice\":\"Finalizes the update process of the optimistic minting fee.\"},\"finalizeUpgrade()\":{\"notice\":\"Allows the governance to finalize vault upgrade process. The upgrade process needs to be first initiated with a call to `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass. Once the upgrade is finalized, the new vault becomes the owner of the TBTC token and receives the whole Bank balance of this vault.\"},\"getMinters()\":{\"notice\":\"Allows to fetch a list of all Minters.\"},\"initiateUpgrade(address)\":{\"notice\":\"Initiates vault upgrade process. The upgrade process needs to be finalized with a call to `finalizeUpgrade` function after the `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can initiate the upgrade.\"},\"isGuardian(address)\":{\"notice\":\"Indicates if the given address is a Guardian. Only Guardians can cancel requested optimistic minting.\"},\"isMinter(address)\":{\"notice\":\"Indicates if the given address is a Minter. Only Minters can request optimistic minting.\"},\"isOptimisticMintingPaused()\":{\"notice\":\"Indicates if the optimistic minting has been paused. Only the Governance can pause optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits.\"},\"mint(uint256)\":{\"notice\":\"Mints the given `amount` of TBTC to the caller previously transferring `amount / SATOSHI_MULTIPLIER` of the Bank balance from caller to TBTC Vault. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's Bank balance.\"},\"minters(uint256)\":{\"notice\":\"List of all Minters.\"},\"newOptimisticMintingDelay()\":{\"notice\":\"New optimistic minting delay value. Set only when the parameter update process is pending. Once the update gets finalized, this\"},\"newOptimisticMintingFeeDivisor()\":{\"notice\":\"New optimistic minting fee divisor value. Set only when the parameter update process is pending. Once the update gets\"},\"newVault()\":{\"notice\":\"The address of a new TBTC vault. Set only when the upgrade process is pending. Once the upgrade gets finalized, the new TBTC vault will become an owner of TBTC token.\"},\"optimisticMintingDebt(address)\":{\"notice\":\"Optimistic minting debt value per depositor's address. The debt represents the total value of all depositor's deposits revealed to the Bridge that has not been yet swept and led to the optimistic minting of TBTC. When `TBTCVault` sweeps a deposit, the debt is fully or partially paid off, no matter if that particular swept deposit was used for the optimistic minting or not. The values are in 1e18 Ethereum precision.\"},\"optimisticMintingDelay()\":{\"notice\":\"The time that needs to pass between the moment the optimistic minting is requested and the moment optimistic minting is finalized with minting TBTC.\"},\"optimisticMintingDelayUpdateInitiatedTimestamp()\":{\"notice\":\"The timestamp at which the update of the optimistic minting delay started. Zero if update is not in progress.\"},\"optimisticMintingFeeDivisor()\":{\"notice\":\"Divisor used to compute the treasury fee taken from each optimistically minted deposit and transferred to the treasury upon finalization of the optimistic mint. This fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2%, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`. The optimistic minting fee does not replace the deposit treasury fee cut by the Bridge. The optimistic fee is a percentage AFTER the treasury fee is cut: `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`\"},\"optimisticMintingFeeUpdateInitiatedTimestamp()\":{\"notice\":\"The timestamp at which the update of the optimistic minting fee divisor started. Zero if update is not in progress.\"},\"optimisticMintingRequests(uint256)\":{\"notice\":\"Collection of all revealed deposits for which the optimistic minting was requested. Indexed by a deposit key computed as `keccak256(fundingTxHash | fundingOutputIndex)`.\"},\"pauseOptimisticMinting()\":{\"notice\":\"Pauses the optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits.\"},\"receiveApproval(address,uint256,address,bytes)\":{\"notice\":\"Burns `amount` of TBTC from the caller's balance. If `extraData` is empty, transfers `amount` back to the caller's balance in the Bank. If `extraData` is not empty, requests redemption in the Bridge using the `extraData` as a `redemptionData` parameter to Bridge's `receiveBalanceApproval` function. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account. Note that it may left a token approval equal to the remainder.\"},\"receiveBalanceApproval(address,uint256,bytes)\":{\"notice\":\"Transfers `satoshis` of the Bank balance from the caller to TBTC Vault and mints `satoshis * SATOSHI_MULTIPLIER` of TBTC to the caller.\"},\"receiveBalanceIncrease(address[],uint256[])\":{\"notice\":\"Mints the same amount of TBTC as the deposited satoshis amount multiplied by SATOSHI_MULTIPLIER for each depositor in the array. Can only be called by the Bank after the Bridge swept deposits and Bank increased balance for the vault.\"},\"recoverERC20(address,address,uint256)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC20 token sent - mistakenly or not - to the vault address. This function should be used to withdraw TBTC v1 tokens transferred to TBTCVault as a result of VendingMachine > TBTCVault upgrade.\"},\"recoverERC20FromToken(address,address,uint256)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC20 token sent mistakenly to the TBTC token contract address.\"},\"recoverERC721(address,address,uint256,bytes)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the vault address.\"},\"recoverERC721FromToken(address,address,uint256,bytes)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the TBTC token contract address.\"},\"removeGuardian(address)\":{\"notice\":\"Removes the address from the Guardian set.\"},\"removeMinter(address)\":{\"notice\":\"Removes the address from the Minter list.\"},\"requestOptimisticMint(bytes32,uint32)\":{\"notice\":\"Allows a Minter to request for an optimistic minting of TBTC. The following conditions must be met: - There is no optimistic minting request for the deposit, finalized or not. - The deposit with the given Bitcoin funding transaction hash and output index has been revealed to the Bridge. - The deposit has not been swept yet. - The deposit is targeted into the TBTCVault. - The optimistic minting is not paused. After calling this function, the Minter has to wait for `optimisticMintingDelay` before finalizing the mint with a call to finalizeOptimisticMint.\"},\"unmint(uint256)\":{\"notice\":\"Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` back to the caller's balance in the Bank. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account.\"},\"unmintAndRedeem(uint256,bytes)\":{\"notice\":\"Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` of Bank balance to the Bridge requesting redemption based on the provided `redemptionData`. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account.\"},\"unpauseOptimisticMinting()\":{\"notice\":\"Unpauses the optimistic minting.\"},\"upgradeInitiatedTimestamp()\":{\"notice\":\"The timestamp at which an upgrade to a new TBTC vault was initiated. Set only when the upgrade process is pending.\"}},\"notice\":\"TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of Bitcoin. It facilitates Bitcoin holders to act on the Ethereum blockchain and access the decentralized finance (DeFi) ecosystem. TBTC Vault mints and unmints TBTC based on Bitcoin balances in the Bank.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/vault/TBTCVault.sol\":\"TBTCVault\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":1000},\"remappings\":[]},\"sources\":{\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/** @title BitcoinSPV */\\n/** @author Summa (https://summa.one) */\\n\\nimport {BytesLib} from \\\"./BytesLib.sol\\\";\\nimport {SafeMath} from \\\"./SafeMath.sol\\\";\\n\\nlibrary BTCUtils {\\n using BytesLib for bytes;\\n using SafeMath for uint256;\\n\\n // The target at minimum Difficulty. Also the target of the genesis block\\n uint256 public constant DIFF1_TARGET = 0xffff0000000000000000000000000000000000000000000000000000;\\n\\n uint256 public constant RETARGET_PERIOD = 2 * 7 * 24 * 60 * 60; // 2 weeks in seconds\\n uint256 public constant RETARGET_PERIOD_BLOCKS = 2016; // 2 weeks in blocks\\n\\n uint256 public constant ERR_BAD_ARG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\\n\\n /* ***** */\\n /* UTILS */\\n /* ***** */\\n\\n /// @notice Determines the length of a VarInt in bytes\\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\\n /// @param _flag The first byte of a VarInt\\n /// @return The number of non-flag bytes in the VarInt\\n function determineVarIntDataLength(bytes memory _flag) internal pure returns (uint8) {\\n return determineVarIntDataLengthAt(_flag, 0);\\n }\\n\\n /// @notice Determines the length of a VarInt in bytes\\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\\n /// @param _b The byte array containing a VarInt\\n /// @param _at The position of the VarInt in the array\\n /// @return The number of non-flag bytes in the VarInt\\n function determineVarIntDataLengthAt(bytes memory _b, uint256 _at) internal pure returns (uint8) {\\n if (uint8(_b[_at]) == 0xff) {\\n return 8; // one-byte flag, 8 bytes data\\n }\\n if (uint8(_b[_at]) == 0xfe) {\\n return 4; // one-byte flag, 4 bytes data\\n }\\n if (uint8(_b[_at]) == 0xfd) {\\n return 2; // one-byte flag, 2 bytes data\\n }\\n\\n return 0; // flag is data\\n }\\n\\n /// @notice Parse a VarInt into its data length and the number it represents\\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\\n /// Caller SHOULD explicitly handle this case (or bubble it up)\\n /// @param _b A byte-string starting with a VarInt\\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\\n function parseVarInt(bytes memory _b) internal pure returns (uint256, uint256) {\\n return parseVarIntAt(_b, 0);\\n }\\n\\n /// @notice Parse a VarInt into its data length and the number it represents\\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\\n /// Caller SHOULD explicitly handle this case (or bubble it up)\\n /// @param _b A byte-string containing a VarInt\\n /// @param _at The position of the VarInt\\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\\n function parseVarIntAt(bytes memory _b, uint256 _at) internal pure returns (uint256, uint256) {\\n uint8 _dataLen = determineVarIntDataLengthAt(_b, _at);\\n\\n if (_dataLen == 0) {\\n return (0, uint8(_b[_at]));\\n }\\n if (_b.length < 1 + _dataLen + _at) {\\n return (ERR_BAD_ARG, 0);\\n }\\n uint256 _number;\\n if (_dataLen == 2) {\\n _number = reverseUint16(uint16(_b.slice2(1 + _at)));\\n } else if (_dataLen == 4) {\\n _number = reverseUint32(uint32(_b.slice4(1 + _at)));\\n } else if (_dataLen == 8) {\\n _number = reverseUint64(uint64(_b.slice8(1 + _at)));\\n }\\n return (_dataLen, _number);\\n }\\n\\n /// @notice Changes the endianness of a byte array\\n /// @dev Returns a new, backwards, bytes\\n /// @param _b The bytes to reverse\\n /// @return The reversed bytes\\n function reverseEndianness(bytes memory _b) internal pure returns (bytes memory) {\\n bytes memory _newValue = new bytes(_b.length);\\n\\n for (uint i = 0; i < _b.length; i++) {\\n _newValue[_b.length - i - 1] = _b[i];\\n }\\n\\n return _newValue;\\n }\\n\\n /// @notice Changes the endianness of a uint256\\n /// @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint256(uint256 _b) internal pure returns (uint256 v) {\\n v = _b;\\n\\n // swap bytes\\n v = ((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |\\n ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);\\n // swap 2-byte long pairs\\n v = ((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |\\n ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);\\n // swap 4-byte long pairs\\n v = ((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |\\n ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);\\n // swap 8-byte long pairs\\n v = ((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |\\n ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);\\n // swap 16-byte long pairs\\n v = (v >> 128) | (v << 128);\\n }\\n\\n /// @notice Changes the endianness of a uint64\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint64(uint64 _b) internal pure returns (uint64 v) {\\n v = _b;\\n\\n // swap bytes\\n v = ((v >> 8) & 0x00FF00FF00FF00FF) |\\n ((v & 0x00FF00FF00FF00FF) << 8);\\n // swap 2-byte long pairs\\n v = ((v >> 16) & 0x0000FFFF0000FFFF) |\\n ((v & 0x0000FFFF0000FFFF) << 16);\\n // swap 4-byte long pairs\\n v = (v >> 32) | (v << 32);\\n }\\n\\n /// @notice Changes the endianness of a uint32\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint32(uint32 _b) internal pure returns (uint32 v) {\\n v = _b;\\n\\n // swap bytes\\n v = ((v >> 8) & 0x00FF00FF) |\\n ((v & 0x00FF00FF) << 8);\\n // swap 2-byte long pairs\\n v = (v >> 16) | (v << 16);\\n }\\n\\n /// @notice Changes the endianness of a uint24\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint24(uint24 _b) internal pure returns (uint24 v) {\\n v = (_b << 16) | (_b & 0x00FF00) | (_b >> 16);\\n }\\n\\n /// @notice Changes the endianness of a uint16\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint16(uint16 _b) internal pure returns (uint16 v) {\\n v = (_b << 8) | (_b >> 8);\\n }\\n\\n\\n /// @notice Converts big-endian bytes to a uint\\n /// @dev Traverses the byte array and sums the bytes\\n /// @param _b The big-endian bytes-encoded integer\\n /// @return The integer representation\\n function bytesToUint(bytes memory _b) internal pure returns (uint256) {\\n uint256 _number;\\n\\n for (uint i = 0; i < _b.length; i++) {\\n _number = _number + uint8(_b[i]) * (2 ** (8 * (_b.length - (i + 1))));\\n }\\n\\n return _number;\\n }\\n\\n /// @notice Get the last _num bytes from a byte array\\n /// @param _b The byte array to slice\\n /// @param _num The number of bytes to extract from the end\\n /// @return The last _num bytes of _b\\n function lastBytes(bytes memory _b, uint256 _num) internal pure returns (bytes memory) {\\n uint256 _start = _b.length.sub(_num);\\n\\n return _b.slice(_start, _num);\\n }\\n\\n /// @notice Implements bitcoin's hash160 (rmd160(sha2()))\\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\\n /// @param _b The pre-image\\n /// @return The digest\\n function hash160(bytes memory _b) internal pure returns (bytes memory) {\\n return abi.encodePacked(ripemd160(abi.encodePacked(sha256(_b))));\\n }\\n\\n /// @notice Implements bitcoin's hash160 (sha2 + ripemd160)\\n /// @dev sha2 precompile at address(2), ripemd160 at address(3)\\n /// @param _b The pre-image\\n /// @return res The digest\\n function hash160View(bytes memory _b) internal view returns (bytes20 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\\n pop(staticcall(gas(), 3, 0x00, 32, 0x00, 32))\\n // read from position 12 = 0c\\n res := mload(0x0c)\\n }\\n }\\n\\n /// @notice Implements bitcoin's hash256 (double sha2)\\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\\n /// @param _b The pre-image\\n /// @return The digest\\n function hash256(bytes memory _b) internal pure returns (bytes32) {\\n return sha256(abi.encodePacked(sha256(_b)));\\n }\\n\\n /// @notice Implements bitcoin's hash256 (double sha2)\\n /// @dev sha2 is precompiled smart contract located at address(2)\\n /// @param _b The pre-image\\n /// @return res The digest\\n function hash256View(bytes memory _b) internal view returns (bytes32 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\\n res := mload(0x00)\\n }\\n }\\n\\n /// @notice Implements bitcoin's hash256 on a pair of bytes32\\n /// @dev sha2 is precompiled smart contract located at address(2)\\n /// @param _a The first bytes32 of the pre-image\\n /// @param _b The second bytes32 of the pre-image\\n /// @return res The digest\\n function hash256Pair(bytes32 _a, bytes32 _b) internal view returns (bytes32 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n mstore(0x00, _a)\\n mstore(0x20, _b)\\n pop(staticcall(gas(), 2, 0x00, 64, 0x00, 32))\\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\\n res := mload(0x00)\\n }\\n }\\n\\n /// @notice Implements bitcoin's hash256 (double sha2)\\n /// @dev sha2 is precompiled smart contract located at address(2)\\n /// @param _b The array containing the pre-image\\n /// @param at The start of the pre-image\\n /// @param len The length of the pre-image\\n /// @return res The digest\\n function hash256Slice(\\n bytes memory _b,\\n uint256 at,\\n uint256 len\\n ) internal view returns (bytes32 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n pop(staticcall(gas(), 2, add(_b, add(32, at)), len, 0x00, 32))\\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\\n res := mload(0x00)\\n }\\n }\\n\\n /* ************ */\\n /* Legacy Input */\\n /* ************ */\\n\\n /// @notice Extracts the nth input from the vin (0-indexed)\\n /// @dev Iterates over the vin. If you need to extract several, write a custom function\\n /// @param _vin The vin as a tightly-packed byte array\\n /// @param _index The 0-indexed location of the input to extract\\n /// @return The input as a byte array\\n function extractInputAtIndex(bytes memory _vin, uint256 _index) internal pure returns (bytes memory) {\\n uint256 _varIntDataLen;\\n uint256 _nIns;\\n\\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Read overrun during VarInt parsing\\\");\\n require(_index < _nIns, \\\"Vin read overrun\\\");\\n\\n uint256 _len = 0;\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 _i = 0; _i < _index; _i ++) {\\n _len = determineInputLengthAt(_vin, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n _offset = _offset + _len;\\n }\\n\\n _len = determineInputLengthAt(_vin, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n return _vin.slice(_offset, _len);\\n }\\n\\n /// @notice Determines whether an input is legacy\\n /// @dev False if no scriptSig, otherwise True\\n /// @param _input The input\\n /// @return True for legacy, False for witness\\n function isLegacyInput(bytes memory _input) internal pure returns (bool) {\\n return _input[36] != hex\\\"00\\\";\\n }\\n\\n /// @notice Determines the length of a scriptSig in an input\\n /// @dev Will return 0 if passed a witness input.\\n /// @param _input The LEGACY input\\n /// @return The length of the script sig\\n function extractScriptSigLen(bytes memory _input) internal pure returns (uint256, uint256) {\\n return extractScriptSigLenAt(_input, 0);\\n }\\n\\n /// @notice Determines the length of a scriptSig in an input\\n /// starting at the specified position\\n /// @dev Will return 0 if passed a witness input.\\n /// @param _input The byte array containing the LEGACY input\\n /// @param _at The position of the input in the array\\n /// @return The length of the script sig\\n function extractScriptSigLenAt(bytes memory _input, uint256 _at) internal pure returns (uint256, uint256) {\\n if (_input.length < 37 + _at) {\\n return (ERR_BAD_ARG, 0);\\n }\\n\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = parseVarIntAt(_input, _at + 36);\\n\\n return (_varIntDataLen, _scriptSigLen);\\n }\\n\\n /// @notice Determines the length of an input from its scriptSig\\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\\n /// @param _input The input\\n /// @return The length of the input in bytes\\n function determineInputLength(bytes memory _input) internal pure returns (uint256) {\\n return determineInputLengthAt(_input, 0);\\n }\\n\\n /// @notice Determines the length of an input from its scriptSig,\\n /// starting at the specified position\\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\\n /// @param _input The byte array containing the input\\n /// @param _at The position of the input in the array\\n /// @return The length of the input in bytes\\n function determineInputLengthAt(bytes memory _input, uint256 _at) internal pure returns (uint256) {\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLenAt(_input, _at);\\n if (_varIntDataLen == ERR_BAD_ARG) {\\n return ERR_BAD_ARG;\\n }\\n\\n return 36 + 1 + _varIntDataLen + _scriptSigLen + 4;\\n }\\n\\n /// @notice Extracts the LE sequence bytes from an input\\n /// @dev Sequence is used for relative time locks\\n /// @param _input The LEGACY input\\n /// @return The sequence bytes (LE uint)\\n function extractSequenceLELegacy(bytes memory _input) internal pure returns (bytes4) {\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n return _input.slice4(36 + 1 + _varIntDataLen + _scriptSigLen);\\n }\\n\\n /// @notice Extracts the sequence from the input\\n /// @dev Sequence is a 4-byte little-endian number\\n /// @param _input The LEGACY input\\n /// @return The sequence number (big-endian uint)\\n function extractSequenceLegacy(bytes memory _input) internal pure returns (uint32) {\\n uint32 _leSeqence = uint32(extractSequenceLELegacy(_input));\\n uint32 _beSequence = reverseUint32(_leSeqence);\\n return _beSequence;\\n }\\n /// @notice Extracts the VarInt-prepended scriptSig from the input in a tx\\n /// @dev Will return hex\\\"00\\\" if passed a witness input\\n /// @param _input The LEGACY input\\n /// @return The length-prepended scriptSig\\n function extractScriptSig(bytes memory _input) internal pure returns (bytes memory) {\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n return _input.slice(36, 1 + _varIntDataLen + _scriptSigLen);\\n }\\n\\n\\n /* ************* */\\n /* Witness Input */\\n /* ************* */\\n\\n /// @notice Extracts the LE sequence bytes from an input\\n /// @dev Sequence is used for relative time locks\\n /// @param _input The WITNESS input\\n /// @return The sequence bytes (LE uint)\\n function extractSequenceLEWitness(bytes memory _input) internal pure returns (bytes4) {\\n return _input.slice4(37);\\n }\\n\\n /// @notice Extracts the sequence from the input in a tx\\n /// @dev Sequence is a 4-byte little-endian number\\n /// @param _input The WITNESS input\\n /// @return The sequence number (big-endian uint)\\n function extractSequenceWitness(bytes memory _input) internal pure returns (uint32) {\\n uint32 _leSeqence = uint32(extractSequenceLEWitness(_input));\\n uint32 _inputeSequence = reverseUint32(_leSeqence);\\n return _inputeSequence;\\n }\\n\\n /// @notice Extracts the outpoint from the input in a tx\\n /// @dev 32-byte tx id with 4-byte index\\n /// @param _input The input\\n /// @return The outpoint (LE bytes of prev tx hash + LE bytes of prev tx index)\\n function extractOutpoint(bytes memory _input) internal pure returns (bytes memory) {\\n return _input.slice(0, 36);\\n }\\n\\n /// @notice Extracts the outpoint tx id from an input\\n /// @dev 32-byte tx id\\n /// @param _input The input\\n /// @return The tx id (little-endian bytes)\\n function extractInputTxIdLE(bytes memory _input) internal pure returns (bytes32) {\\n return _input.slice32(0);\\n }\\n\\n /// @notice Extracts the outpoint tx id from an input\\n /// starting at the specified position\\n /// @dev 32-byte tx id\\n /// @param _input The byte array containing the input\\n /// @param _at The position of the input\\n /// @return The tx id (little-endian bytes)\\n function extractInputTxIdLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes32) {\\n return _input.slice32(_at);\\n }\\n\\n /// @notice Extracts the LE tx input index from the input in a tx\\n /// @dev 4-byte tx index\\n /// @param _input The input\\n /// @return The tx index (little-endian bytes)\\n function extractTxIndexLE(bytes memory _input) internal pure returns (bytes4) {\\n return _input.slice4(32);\\n }\\n\\n /// @notice Extracts the LE tx input index from the input in a tx\\n /// starting at the specified position\\n /// @dev 4-byte tx index\\n /// @param _input The byte array containing the input\\n /// @param _at The position of the input\\n /// @return The tx index (little-endian bytes)\\n function extractTxIndexLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes4) {\\n return _input.slice4(32 + _at);\\n }\\n\\n /* ****** */\\n /* Output */\\n /* ****** */\\n\\n /// @notice Determines the length of an output\\n /// @dev Works with any properly formatted output\\n /// @param _output The output\\n /// @return The length indicated by the prefix, error if invalid length\\n function determineOutputLength(bytes memory _output) internal pure returns (uint256) {\\n return determineOutputLengthAt(_output, 0);\\n }\\n\\n /// @notice Determines the length of an output\\n /// starting at the specified position\\n /// @dev Works with any properly formatted output\\n /// @param _output The byte array containing the output\\n /// @param _at The position of the output\\n /// @return The length indicated by the prefix, error if invalid length\\n function determineOutputLengthAt(bytes memory _output, uint256 _at) internal pure returns (uint256) {\\n if (_output.length < 9 + _at) {\\n return ERR_BAD_ARG;\\n }\\n uint256 _varIntDataLen;\\n uint256 _scriptPubkeyLength;\\n (_varIntDataLen, _scriptPubkeyLength) = parseVarIntAt(_output, 8 + _at);\\n\\n if (_varIntDataLen == ERR_BAD_ARG) {\\n return ERR_BAD_ARG;\\n }\\n\\n // 8-byte value, 1-byte for tag itself\\n return 8 + 1 + _varIntDataLen + _scriptPubkeyLength;\\n }\\n\\n /// @notice Extracts the output at a given index in the TxOuts vector\\n /// @dev Iterates over the vout. If you need to extract multiple, write a custom function\\n /// @param _vout The _vout to extract from\\n /// @param _index The 0-indexed location of the output to extract\\n /// @return The specified output\\n function extractOutputAtIndex(bytes memory _vout, uint256 _index) internal pure returns (bytes memory) {\\n uint256 _varIntDataLen;\\n uint256 _nOuts;\\n\\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Read overrun during VarInt parsing\\\");\\n require(_index < _nOuts, \\\"Vout read overrun\\\");\\n\\n uint256 _len = 0;\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 _i = 0; _i < _index; _i ++) {\\n _len = determineOutputLengthAt(_vout, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptPubkey\\\");\\n _offset += _len;\\n }\\n\\n _len = determineOutputLengthAt(_vout, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptPubkey\\\");\\n return _vout.slice(_offset, _len);\\n }\\n\\n /// @notice Extracts the value bytes from the output in a tx\\n /// @dev Value is an 8-byte little-endian number\\n /// @param _output The output\\n /// @return The output value as LE bytes\\n function extractValueLE(bytes memory _output) internal pure returns (bytes8) {\\n return _output.slice8(0);\\n }\\n\\n /// @notice Extracts the value from the output in a tx\\n /// @dev Value is an 8-byte little-endian number\\n /// @param _output The output\\n /// @return The output value\\n function extractValue(bytes memory _output) internal pure returns (uint64) {\\n uint64 _leValue = uint64(extractValueLE(_output));\\n uint64 _beValue = reverseUint64(_leValue);\\n return _beValue;\\n }\\n\\n /// @notice Extracts the value from the output in a tx\\n /// @dev Value is an 8-byte little-endian number\\n /// @param _output The byte array containing the output\\n /// @param _at The starting index of the output in the array\\n /// @return The output value\\n function extractValueAt(bytes memory _output, uint256 _at) internal pure returns (uint64) {\\n uint64 _leValue = uint64(_output.slice8(_at));\\n uint64 _beValue = reverseUint64(_leValue);\\n return _beValue;\\n }\\n\\n /// @notice Extracts the data from an op return output\\n /// @dev Returns hex\\\"\\\" if no data or not an op return\\n /// @param _output The output\\n /// @return Any data contained in the opreturn output, null if not an op return\\n function extractOpReturnData(bytes memory _output) internal pure returns (bytes memory) {\\n if (_output[9] != hex\\\"6a\\\") {\\n return hex\\\"\\\";\\n }\\n bytes1 _dataLen = _output[10];\\n return _output.slice(11, uint256(uint8(_dataLen)));\\n }\\n\\n /// @notice Extracts the hash from the output script\\n /// @dev Determines type by the length prefix and validates format\\n /// @param _output The output\\n /// @return The hash committed to by the pk_script, or null for errors\\n function extractHash(bytes memory _output) internal pure returns (bytes memory) {\\n return extractHashAt(_output, 8, _output.length - 8);\\n }\\n\\n /// @notice Extracts the hash from the output script\\n /// @dev Determines type by the length prefix and validates format\\n /// @param _output The byte array containing the output\\n /// @param _at The starting index of the output script in the array\\n /// (output start + 8)\\n /// @param _len The length of the output script\\n /// (output length - 8)\\n /// @return The hash committed to by the pk_script, or null for errors\\n function extractHashAt(\\n bytes memory _output,\\n uint256 _at,\\n uint256 _len\\n ) internal pure returns (bytes memory) {\\n uint8 _scriptLen = uint8(_output[_at]);\\n\\n // don't have to worry about overflow here.\\n // if _scriptLen + 1 overflows, then output length would have to be < 1\\n // for this check to pass. if it's < 1, then we errored when assigning\\n // _scriptLen\\n if (_scriptLen + 1 != _len) {\\n return hex\\\"\\\";\\n }\\n\\n if (uint8(_output[_at + 1]) == 0) {\\n if (_scriptLen < 2) {\\n return hex\\\"\\\";\\n }\\n uint256 _payloadLen = uint8(_output[_at + 2]);\\n // Check for maliciously formatted witness outputs.\\n // No need to worry about underflow as long b/c of the `< 2` check\\n if (_payloadLen != _scriptLen - 2 || (_payloadLen != 0x20 && _payloadLen != 0x14)) {\\n return hex\\\"\\\";\\n }\\n return _output.slice(_at + 3, _payloadLen);\\n } else {\\n bytes3 _tag = _output.slice3(_at);\\n // p2pkh\\n if (_tag == hex\\\"1976a9\\\") {\\n // Check for maliciously formatted p2pkh\\n // No need to worry about underflow, b/c of _scriptLen check\\n if (uint8(_output[_at + 3]) != 0x14 ||\\n _output.slice2(_at + _len - 2) != hex\\\"88ac\\\") {\\n return hex\\\"\\\";\\n }\\n return _output.slice(_at + 4, 20);\\n //p2sh\\n } else if (_tag == hex\\\"17a914\\\") {\\n // Check for maliciously formatted p2sh\\n // No need to worry about underflow, b/c of _scriptLen check\\n if (uint8(_output[_at + _len - 1]) != 0x87) {\\n return hex\\\"\\\";\\n }\\n return _output.slice(_at + 3, 20);\\n }\\n }\\n return hex\\\"\\\"; /* NB: will trigger on OPRETURN and any non-standard that doesn't overrun */\\n }\\n\\n /* ********** */\\n /* Witness TX */\\n /* ********** */\\n\\n\\n /// @notice Checks that the vin passed up is properly formatted\\n /// @dev Consider a vin with a valid vout in its scriptsig\\n /// @param _vin Raw bytes length-prefixed input vector\\n /// @return True if it represents a validly formatted vin\\n function validateVin(bytes memory _vin) internal pure returns (bool) {\\n uint256 _varIntDataLen;\\n uint256 _nIns;\\n\\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\\n\\n // Not valid if it says there are too many or no inputs\\n if (_nIns == 0 || _varIntDataLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 i = 0; i < _nIns; i++) {\\n // If we're at the end, but still expect more\\n if (_offset >= _vin.length) {\\n return false;\\n }\\n\\n // Grab the next input and determine its length.\\n uint256 _nextLen = determineInputLengthAt(_vin, _offset);\\n if (_nextLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n // Increase the offset by that much\\n _offset += _nextLen;\\n }\\n\\n // Returns false if we're not exactly at the end\\n return _offset == _vin.length;\\n }\\n\\n /// @notice Checks that the vout passed up is properly formatted\\n /// @dev Consider a vout with a valid scriptpubkey\\n /// @param _vout Raw bytes length-prefixed output vector\\n /// @return True if it represents a validly formatted vout\\n function validateVout(bytes memory _vout) internal pure returns (bool) {\\n uint256 _varIntDataLen;\\n uint256 _nOuts;\\n\\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\\n\\n // Not valid if it says there are too many or no outputs\\n if (_nOuts == 0 || _varIntDataLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 i = 0; i < _nOuts; i++) {\\n // If we're at the end, but still expect more\\n if (_offset >= _vout.length) {\\n return false;\\n }\\n\\n // Grab the next output and determine its length.\\n // Increase the offset by that much\\n uint256 _nextLen = determineOutputLengthAt(_vout, _offset);\\n if (_nextLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n _offset += _nextLen;\\n }\\n\\n // Returns false if we're not exactly at the end\\n return _offset == _vout.length;\\n }\\n\\n\\n\\n /* ************ */\\n /* Block Header */\\n /* ************ */\\n\\n /// @notice Extracts the transaction merkle root from a block header\\n /// @dev Use verifyHash256Merkle to verify proofs with this root\\n /// @param _header The header\\n /// @return The merkle root (little-endian)\\n function extractMerkleRootLE(bytes memory _header) internal pure returns (bytes32) {\\n return _header.slice32(36);\\n }\\n\\n /// @notice Extracts the target from a block header\\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\\n /// @param _header The header\\n /// @return The target threshold\\n function extractTarget(bytes memory _header) internal pure returns (uint256) {\\n return extractTargetAt(_header, 0);\\n }\\n\\n /// @notice Extracts the target from a block header\\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\\n /// @param _header The array containing the header\\n /// @param at The start of the header\\n /// @return The target threshold\\n function extractTargetAt(bytes memory _header, uint256 at) internal pure returns (uint256) {\\n uint24 _m = uint24(_header.slice3(72 + at));\\n uint8 _e = uint8(_header[75 + at]);\\n uint256 _mantissa = uint256(reverseUint24(_m));\\n uint _exponent = _e - 3;\\n\\n return _mantissa * (256 ** _exponent);\\n }\\n\\n /// @notice Calculate difficulty from the difficulty 1 target and current target\\n /// @dev Difficulty 1 is 0x1d00ffff on mainnet and testnet\\n /// @dev Difficulty 1 is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\\n /// @param _target The current target\\n /// @return The block difficulty (bdiff)\\n function calculateDifficulty(uint256 _target) internal pure returns (uint256) {\\n // Difficulty 1 calculated from 0x1d00ffff\\n return DIFF1_TARGET.div(_target);\\n }\\n\\n /// @notice Extracts the previous block's hash from a block header\\n /// @dev Block headers do NOT include block number :(\\n /// @param _header The header\\n /// @return The previous block's hash (little-endian)\\n function extractPrevBlockLE(bytes memory _header) internal pure returns (bytes32) {\\n return _header.slice32(4);\\n }\\n\\n /// @notice Extracts the previous block's hash from a block header\\n /// @dev Block headers do NOT include block number :(\\n /// @param _header The array containing the header\\n /// @param at The start of the header\\n /// @return The previous block's hash (little-endian)\\n function extractPrevBlockLEAt(\\n bytes memory _header,\\n uint256 at\\n ) internal pure returns (bytes32) {\\n return _header.slice32(4 + at);\\n }\\n\\n /// @notice Extracts the timestamp from a block header\\n /// @dev Time is not 100% reliable\\n /// @param _header The header\\n /// @return The timestamp (little-endian bytes)\\n function extractTimestampLE(bytes memory _header) internal pure returns (bytes4) {\\n return _header.slice4(68);\\n }\\n\\n /// @notice Extracts the timestamp from a block header\\n /// @dev Time is not 100% reliable\\n /// @param _header The header\\n /// @return The timestamp (uint)\\n function extractTimestamp(bytes memory _header) internal pure returns (uint32) {\\n return reverseUint32(uint32(extractTimestampLE(_header)));\\n }\\n\\n /// @notice Extracts the expected difficulty from a block header\\n /// @dev Does NOT verify the work\\n /// @param _header The header\\n /// @return The difficulty as an integer\\n function extractDifficulty(bytes memory _header) internal pure returns (uint256) {\\n return calculateDifficulty(extractTarget(_header));\\n }\\n\\n /// @notice Concatenates and hashes two inputs for merkle proving\\n /// @param _a The first hash\\n /// @param _b The second hash\\n /// @return The double-sha256 of the concatenated hashes\\n function _hash256MerkleStep(bytes memory _a, bytes memory _b) internal view returns (bytes32) {\\n return hash256View(abi.encodePacked(_a, _b));\\n }\\n\\n /// @notice Concatenates and hashes two inputs for merkle proving\\n /// @param _a The first hash\\n /// @param _b The second hash\\n /// @return The double-sha256 of the concatenated hashes\\n function _hash256MerkleStep(bytes32 _a, bytes32 _b) internal view returns (bytes32) {\\n return hash256Pair(_a, _b);\\n }\\n\\n\\n /// @notice Verifies a Bitcoin-style merkle tree\\n /// @dev Leaves are 0-indexed. Inefficient version.\\n /// @param _proof The proof. Tightly packed LE sha256 hashes. The last hash is the root\\n /// @param _index The index of the leaf\\n /// @return true if the proof is valid, else false\\n function verifyHash256Merkle(bytes memory _proof, uint _index) internal view returns (bool) {\\n // Not an even number of hashes\\n if (_proof.length % 32 != 0) {\\n return false;\\n }\\n\\n // Special case for coinbase-only blocks\\n if (_proof.length == 32) {\\n return true;\\n }\\n\\n // Should never occur\\n if (_proof.length == 64) {\\n return false;\\n }\\n\\n bytes32 _root = _proof.slice32(_proof.length - 32);\\n bytes32 _current = _proof.slice32(0);\\n bytes memory _tree = _proof.slice(32, _proof.length - 64);\\n\\n return verifyHash256Merkle(_current, _tree, _root, _index);\\n }\\n\\n /// @notice Verifies a Bitcoin-style merkle tree\\n /// @dev Leaves are 0-indexed. Efficient version.\\n /// @param _leaf The leaf of the proof. LE sha256 hash.\\n /// @param _tree The intermediate nodes in the proof.\\n /// Tightly packed LE sha256 hashes.\\n /// @param _root The root of the proof. LE sha256 hash.\\n /// @param _index The index of the leaf\\n /// @return true if the proof is valid, else false\\n function verifyHash256Merkle(\\n bytes32 _leaf,\\n bytes memory _tree,\\n bytes32 _root,\\n uint _index\\n ) internal view returns (bool) {\\n // Not an even number of hashes\\n if (_tree.length % 32 != 0) {\\n return false;\\n }\\n\\n // Should never occur\\n if (_tree.length == 0) {\\n return false;\\n }\\n\\n uint _idx = _index;\\n bytes32 _current = _leaf;\\n\\n // i moves in increments of 32\\n for (uint i = 0; i < _tree.length; i += 32) {\\n if (_idx % 2 == 1) {\\n _current = _hash256MerkleStep(_tree.slice32(i), _current);\\n } else {\\n _current = _hash256MerkleStep(_current, _tree.slice32(i));\\n }\\n _idx = _idx >> 1;\\n }\\n return _current == _root;\\n }\\n\\n /*\\n NB: https://github.com/bitcoin/bitcoin/blob/78dae8caccd82cfbfd76557f1fb7d7557c7b5edb/src/pow.cpp#L49-L72\\n NB: We get a full-bitlength target from this. For comparison with\\n header-encoded targets we need to mask it with the header target\\n e.g. (full & truncated) == truncated\\n */\\n /// @notice performs the bitcoin difficulty retarget\\n /// @dev implements the Bitcoin algorithm precisely\\n /// @param _previousTarget the target of the previous period\\n /// @param _firstTimestamp the timestamp of the first block in the difficulty period\\n /// @param _secondTimestamp the timestamp of the last block in the difficulty period\\n /// @return the new period's target threshold\\n function retargetAlgorithm(\\n uint256 _previousTarget,\\n uint256 _firstTimestamp,\\n uint256 _secondTimestamp\\n ) internal pure returns (uint256) {\\n uint256 _elapsedTime = _secondTimestamp.sub(_firstTimestamp);\\n\\n // Normalize ratio to factor of 4 if very long or very short\\n if (_elapsedTime < RETARGET_PERIOD.div(4)) {\\n _elapsedTime = RETARGET_PERIOD.div(4);\\n }\\n if (_elapsedTime > RETARGET_PERIOD.mul(4)) {\\n _elapsedTime = RETARGET_PERIOD.mul(4);\\n }\\n\\n /*\\n NB: high targets e.g. ffff0020 can cause overflows here\\n so we divide it by 256**2, then multiply by 256**2 later\\n we know the target is evenly divisible by 256**2, so this isn't an issue\\n */\\n\\n uint256 _adjusted = _previousTarget.div(65536).mul(_elapsedTime);\\n return _adjusted.div(RETARGET_PERIOD).mul(65536);\\n }\\n}\\n\",\"keccak256\":\"0x439eaa97e9239705f3d31e8d39dccbad32311f1f119e295d53c65e0ae3c5a5fc\"},\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/*\\n\\nhttps://github.com/GNSPS/solidity-bytes-utils/\\n\\nThis is free and unencumbered software released into the public domain.\\n\\nAnyone is free to copy, modify, publish, use, compile, sell, or\\ndistribute this software, either in source code form or as a compiled\\nbinary, for any purpose, commercial or non-commercial, and by any\\nmeans.\\n\\nIn jurisdictions that recognize copyright laws, the author or authors\\nof this software dedicate any and all copyright interest in the\\nsoftware to the public domain. We make this dedication for the benefit\\nof the public at large and to the detriment of our heirs and\\nsuccessors. We intend this dedication to be an overt act of\\nrelinquishment in perpetuity of all present and future rights to this\\nsoftware under copyright law.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND,\\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\\nOTHER DEALINGS IN THE SOFTWARE.\\n\\nFor more information, please refer to \\n*/\\n\\n\\n/** @title BytesLib **/\\n/** @author https://github.com/GNSPS **/\\n\\nlibrary BytesLib {\\n function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {\\n bytes memory tempBytes;\\n\\n assembly {\\n // Get a location of some free memory and store it in tempBytes as\\n // Solidity does for memory variables.\\n tempBytes := mload(0x40)\\n\\n // Store the length of the first bytes array at the beginning of\\n // the memory for tempBytes.\\n let length := mload(_preBytes)\\n mstore(tempBytes, length)\\n\\n // Maintain a memory counter for the current write location in the\\n // temp bytes array by adding the 32 bytes for the array length to\\n // the starting location.\\n let mc := add(tempBytes, 0x20)\\n // Stop copying when the memory counter reaches the length of the\\n // first bytes array.\\n let end := add(mc, length)\\n\\n for {\\n // Initialize a copy counter to the start of the _preBytes data,\\n // 32 bytes into its memory.\\n let cc := add(_preBytes, 0x20)\\n } lt(mc, end) {\\n // Increase both counters by 32 bytes each iteration.\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // Write the _preBytes data into the tempBytes memory 32 bytes\\n // at a time.\\n mstore(mc, mload(cc))\\n }\\n\\n // Add the length of _postBytes to the current length of tempBytes\\n // and store it as the new length in the first 32 bytes of the\\n // tempBytes memory.\\n length := mload(_postBytes)\\n mstore(tempBytes, add(length, mload(tempBytes)))\\n\\n // Move the memory counter back from a multiple of 0x20 to the\\n // actual end of the _preBytes data.\\n mc := end\\n // Stop copying when the memory counter reaches the new combined\\n // length of the arrays.\\n end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n } lt(mc, end) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n mstore(mc, mload(cc))\\n }\\n\\n // Update the free-memory pointer by padding our last write location\\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\\n // next 32 byte block, then round down to the nearest multiple of\\n // 32. If the sum of the length of the two arrays is zero then add\\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\\n mstore(0x40, and(\\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\\n not(31) // Round down to the nearest 32 bytes.\\n ))\\n }\\n\\n return tempBytes;\\n }\\n\\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {\\n assembly {\\n // Read the first 32 bytes of _preBytes storage, which is the length\\n // of the array. (We don't need to use the offset into the slot\\n // because arrays use the entire slot.)\\n let fslot := sload(_preBytes.slot)\\n // Arrays of 31 bytes or less have an even value in their slot,\\n // while longer arrays have an odd value. The actual length is\\n // the slot divided by two for odd values, and the lowest order\\n // byte divided by two for even values.\\n // If the slot is even, bitwise and the slot with 255 and divide by\\n // two to get the length. If the slot is odd, bitwise and the slot\\n // with -1 and divide by two.\\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\\n let mlength := mload(_postBytes)\\n let newlength := add(slength, mlength)\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n switch add(lt(slength, 32), lt(newlength, 32))\\n case 2 {\\n // Since the new array still fits in the slot, we just need to\\n // update the contents of the slot.\\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\\n sstore(\\n _preBytes.slot,\\n // all the modifications to the slot are inside this\\n // next block\\n add(\\n // we can just add to the slot contents because the\\n // bytes we want to change are the LSBs\\n fslot,\\n add(\\n mul(\\n div(\\n // load the bytes from memory\\n mload(add(_postBytes, 0x20)),\\n // zero all bytes to the right\\n exp(0x100, sub(32, mlength))\\n ),\\n // and now shift left the number of bytes to\\n // leave space for the length in the slot\\n exp(0x100, sub(32, newlength))\\n ),\\n // increase length by the double of the memory\\n // bytes length\\n mul(mlength, 2)\\n )\\n )\\n )\\n }\\n case 1 {\\n // The stored value fits in the slot, but the combined value\\n // will exceed it.\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // The contents of the _postBytes array start 32 bytes into\\n // the structure. Our first read should obtain the `submod`\\n // bytes that can fit into the unused space in the last word\\n // of the stored array. To get this, we read 32 bytes starting\\n // from `submod`, so the data we read overlaps with the array\\n // contents by `submod` bytes. Masking the lowest-order\\n // `submod` bytes allows us to add that value directly to the\\n // stored value.\\n\\n let submod := sub(32, slength)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(\\n sc,\\n add(\\n and(\\n fslot,\\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\\n ),\\n and(mload(mc), mask)\\n )\\n )\\n\\n for {\\n mc := add(mc, 0x20)\\n sc := add(sc, 1)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n default {\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n // Start copying to the last used word of the stored array.\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // Copy over the first `submod` bytes of the new data as in\\n // case 1 above.\\n let slengthmod := mod(slength, 32)\\n let mlengthmod := mod(mlength, 32)\\n let submod := sub(32, slengthmod)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\\n\\n for {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n }\\n }\\n\\n function slice(bytes memory _bytes, uint _start, uint _length) internal pure returns (bytes memory res) {\\n if (_length == 0) {\\n return hex\\\"\\\";\\n }\\n uint _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n // Alloc bytes array with additional 32 bytes afterspace and assign it's size\\n res := mload(0x40)\\n mstore(0x40, add(add(res, 64), _length))\\n mstore(res, _length)\\n\\n // Compute distance between source and destination pointers\\n let diff := sub(res, add(_bytes, _start))\\n\\n for {\\n let src := add(add(_bytes, 32), _start)\\n let end := add(src, _length)\\n } lt(src, end) {\\n src := add(src, 32)\\n } {\\n mstore(add(src, diff), mload(src))\\n }\\n }\\n }\\n\\n /// @notice Take a slice of the byte array, overwriting the destination.\\n /// The length of the slice will equal the length of the destination array.\\n /// @dev Make sure the destination array has afterspace if required.\\n /// @param _bytes The source array\\n /// @param _dest The destination array.\\n /// @param _start The location to start in the source array.\\n function sliceInPlace(\\n bytes memory _bytes,\\n bytes memory _dest,\\n uint _start\\n ) internal pure {\\n uint _length = _dest.length;\\n uint _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n for {\\n let src := add(add(_bytes, 32), _start)\\n let res := add(_dest, 32)\\n let end := add(src, _length)\\n } lt(src, end) {\\n src := add(src, 32)\\n res := add(res, 32)\\n } {\\n mstore(res, mload(src))\\n }\\n }\\n }\\n\\n // Static slice functions, no bounds checking\\n /// @notice take a 32-byte slice from the specified position\\n function slice32(bytes memory _bytes, uint _start) internal pure returns (bytes32 res) {\\n assembly {\\n res := mload(add(add(_bytes, 32), _start))\\n }\\n }\\n\\n /// @notice take a 20-byte slice from the specified position\\n function slice20(bytes memory _bytes, uint _start) internal pure returns (bytes20) {\\n return bytes20(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 8-byte slice from the specified position\\n function slice8(bytes memory _bytes, uint _start) internal pure returns (bytes8) {\\n return bytes8(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 4-byte slice from the specified position\\n function slice4(bytes memory _bytes, uint _start) internal pure returns (bytes4) {\\n return bytes4(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 3-byte slice from the specified position\\n function slice3(bytes memory _bytes, uint _start) internal pure returns (bytes3) {\\n return bytes3(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 2-byte slice from the specified position\\n function slice2(bytes memory _bytes, uint _start) internal pure returns (bytes2) {\\n return bytes2(slice32(_bytes, _start));\\n }\\n\\n function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) {\\n uint _totalLen = _start + 20;\\n require(_totalLen > _start && _bytes.length >= _totalLen, \\\"Address conversion out of bounds.\\\");\\n address tempAddress;\\n\\n assembly {\\n tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)\\n }\\n\\n return tempAddress;\\n }\\n\\n function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) {\\n uint _totalLen = _start + 32;\\n require(_totalLen > _start && _bytes.length >= _totalLen, \\\"Uint conversion out of bounds.\\\");\\n uint256 tempUint;\\n\\n assembly {\\n tempUint := mload(add(add(_bytes, 0x20), _start))\\n }\\n\\n return tempUint;\\n }\\n\\n function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {\\n bool success = true;\\n\\n assembly {\\n let length := mload(_preBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(length, mload(_postBytes))\\n case 1 {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n let mc := add(_preBytes, 0x20)\\n let end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n } eq(add(lt(mc, end), cb), 2) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // if any of these checks fails then arrays are not equal\\n if iszero(eq(mload(mc), mload(cc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {\\n bool success = true;\\n\\n assembly {\\n // we know _preBytes_offset is 0\\n let fslot := sload(_preBytes.slot)\\n // Decode the length of the stored array like in concatStorage().\\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\\n let mlength := mload(_postBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(slength, mlength)\\n case 1 {\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n if iszero(iszero(slength)) {\\n switch lt(slength, 32)\\n case 1 {\\n // blank the last byte which is the length\\n fslot := mul(div(fslot, 0x100), 0x100)\\n\\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\\n // unsuccess:\\n success := 0\\n }\\n }\\n default {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := keccak256(0x0, 0x20)\\n\\n let mc := add(_postBytes, 0x20)\\n let end := add(mc, mlength)\\n\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n for {} eq(add(lt(mc, end), cb), 2) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n if iszero(eq(sload(sc), mload(mc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function toBytes32(bytes memory _source) pure internal returns (bytes32 result) {\\n if (_source.length == 0) {\\n return 0x0;\\n }\\n\\n assembly {\\n result := mload(add(_source, 32))\\n }\\n }\\n\\n function keccak256Slice(bytes memory _bytes, uint _start, uint _length) pure internal returns (bytes32 result) {\\n uint _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n result := keccak256(add(add(_bytes, 32), _start), _length)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x43e0f3b3b23c861bd031588bf410dfdd02e2af17941a89aa38d70e534e0380d1\"},\"@keep-network/bitcoin-spv-sol/contracts/CheckBitcoinSigs.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/** @title CheckBitcoinSigs */\\n/** @author Summa (https://summa.one) */\\n\\nimport {BytesLib} from \\\"./BytesLib.sol\\\";\\nimport {BTCUtils} from \\\"./BTCUtils.sol\\\";\\n\\n\\nlibrary CheckBitcoinSigs {\\n\\n using BytesLib for bytes;\\n using BTCUtils for bytes;\\n\\n /// @notice Derives an Ethereum Account address from a pubkey\\n /// @dev The address is the last 20 bytes of the keccak256 of the address\\n /// @param _pubkey The public key X & Y. Unprefixed, as a 64-byte array\\n /// @return The account address\\n function accountFromPubkey(bytes memory _pubkey) internal pure returns (address) {\\n require(_pubkey.length == 64, \\\"Pubkey must be 64-byte raw, uncompressed key.\\\");\\n\\n // keccak hash of uncompressed unprefixed pubkey\\n bytes32 _digest = keccak256(_pubkey);\\n return address(uint160(uint256(_digest)));\\n }\\n\\n /// @notice Calculates the p2wpkh output script of a pubkey\\n /// @dev Compresses keys to 33 bytes as required by Bitcoin\\n /// @param _pubkey The public key, compressed or uncompressed\\n /// @return The p2wkph output script\\n function p2wpkhFromPubkey(bytes memory _pubkey) internal view returns (bytes memory) {\\n bytes memory _compressedPubkey;\\n uint8 _prefix;\\n\\n if (_pubkey.length == 64) {\\n _prefix = uint8(_pubkey[_pubkey.length - 1]) % 2 == 1 ? 3 : 2;\\n _compressedPubkey = abi.encodePacked(_prefix, _pubkey.slice32(0));\\n } else if (_pubkey.length == 65) {\\n _prefix = uint8(_pubkey[_pubkey.length - 1]) % 2 == 1 ? 3 : 2;\\n _compressedPubkey = abi.encodePacked(_prefix, _pubkey.slice32(1));\\n } else {\\n _compressedPubkey = _pubkey;\\n }\\n\\n require(_compressedPubkey.length == 33, \\\"Witness PKH requires compressed keys\\\");\\n\\n bytes20 _pubkeyHash = _compressedPubkey.hash160View();\\n return abi.encodePacked(hex\\\"0014\\\", _pubkeyHash);\\n }\\n\\n /// @notice checks a signed message's validity under a pubkey\\n /// @dev does this using ecrecover because Ethereum has no soul\\n /// @param _pubkey the public key to check (64 bytes)\\n /// @param _digest the message digest signed\\n /// @param _v the signature recovery value\\n /// @param _r the signature r value\\n /// @param _s the signature s value\\n /// @return true if signature is valid, else false\\n function checkSig(\\n bytes memory _pubkey,\\n bytes32 _digest,\\n uint8 _v,\\n bytes32 _r,\\n bytes32 _s\\n ) internal pure returns (bool) {\\n require(_pubkey.length == 64, \\\"Requires uncompressed unprefixed pubkey\\\");\\n address _expected = accountFromPubkey(_pubkey);\\n address _actual = ecrecover(_digest, _v, _r, _s);\\n return _actual == _expected;\\n }\\n\\n /// @notice checks a signed message against a bitcoin p2wpkh output script\\n /// @dev does this my verifying the p2wpkh matches an ethereum account\\n /// @param _p2wpkhOutputScript the bitcoin output script\\n /// @param _pubkey the uncompressed, unprefixed public key to check\\n /// @param _digest the message digest signed\\n /// @param _v the signature recovery value\\n /// @param _r the signature r value\\n /// @param _s the signature s value\\n /// @return true if signature is valid, else false\\n function checkBitcoinSig(\\n bytes memory _p2wpkhOutputScript,\\n bytes memory _pubkey,\\n bytes32 _digest,\\n uint8 _v,\\n bytes32 _r,\\n bytes32 _s\\n ) internal view returns (bool) {\\n require(_pubkey.length == 64, \\\"Requires uncompressed unprefixed pubkey\\\");\\n\\n bool _isExpectedSigner = keccak256(p2wpkhFromPubkey(_pubkey)) == keccak256(_p2wpkhOutputScript); // is it the expected signer?\\n if (!_isExpectedSigner) {return false;}\\n\\n bool _sigResult = checkSig(_pubkey, _digest, _v, _r, _s);\\n return _sigResult;\\n }\\n\\n /// @notice checks if a message is the sha256 preimage of a digest\\n /// @dev this is NOT the hash256! this step is necessary for ECDSA security!\\n /// @param _digest the digest\\n /// @param _candidate the purported preimage\\n /// @return true if the preimage matches the digest, else false\\n function isSha256Preimage(\\n bytes memory _candidate,\\n bytes32 _digest\\n ) internal pure returns (bool) {\\n return sha256(_candidate) == _digest;\\n }\\n\\n /// @notice checks if a message is the keccak256 preimage of a digest\\n /// @dev this step is necessary for ECDSA security!\\n /// @param _digest the digest\\n /// @param _candidate the purported preimage\\n /// @return true if the preimage matches the digest, else false\\n function isKeccak256Preimage(\\n bytes memory _candidate,\\n bytes32 _digest\\n ) internal pure returns (bool) {\\n return keccak256(_candidate) == _digest;\\n }\\n\\n /// @notice calculates the signature hash of a Bitcoin transaction with the provided details\\n /// @dev documented in bip143. many values are hardcoded here\\n /// @param _outpoint the bitcoin UTXO id (32-byte txid + 4-byte output index)\\n /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey))\\n /// @param _inputValue the value of the input in satoshi\\n /// @param _outputValue the value of the output in satoshi\\n /// @param _outputScript the length-prefixed output script\\n /// @return the double-sha256 (hash256) signature hash as defined by bip143\\n function wpkhSpendSighash(\\n bytes memory _outpoint, // 36-byte UTXO id\\n bytes20 _inputPKH, // 20-byte hash160\\n bytes8 _inputValue, // 8-byte LE\\n bytes8 _outputValue, // 8-byte LE\\n bytes memory _outputScript // lenght-prefixed output script\\n ) internal view returns (bytes32) {\\n // Fixes elements to easily make a 1-in 1-out sighash digest\\n // Does not support timelocks\\n // bytes memory _scriptCode = abi.encodePacked(\\n // hex\\\"1976a914\\\", // length, dup, hash160, pkh_length\\n // _inputPKH,\\n // hex\\\"88ac\\\"); // equal, checksig\\n\\n bytes32 _hashOutputs = abi.encodePacked(\\n _outputValue, // 8-byte LE\\n _outputScript).hash256View();\\n\\n bytes memory _sighashPreimage = abi.encodePacked(\\n hex\\\"01000000\\\", // version\\n _outpoint.hash256View(), // hashPrevouts\\n hex\\\"8cb9012517c817fead650287d61bdd9c68803b6bf9c64133dcab3e65b5a50cb9\\\", // hashSequence(00000000)\\n _outpoint, // outpoint\\n // p2wpkh script code\\n hex\\\"1976a914\\\", // length, dup, hash160, pkh_length\\n _inputPKH,\\n hex\\\"88ac\\\", // equal, checksig\\n // end script code\\n _inputValue, // value of the input in 8-byte LE\\n hex\\\"00000000\\\", // input nSequence\\n _hashOutputs, // hash of the single output\\n hex\\\"00000000\\\", // nLockTime\\n hex\\\"01000000\\\" // SIGHASH_ALL\\n );\\n return _sighashPreimage.hash256View();\\n }\\n\\n /// @notice calculates the signature hash of a Bitcoin transaction with the provided details\\n /// @dev documented in bip143. many values are hardcoded here\\n /// @param _outpoint the bitcoin UTXO id (32-byte txid + 4-byte output index)\\n /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey))\\n /// @param _inputValue the value of the input in satoshi\\n /// @param _outputValue the value of the output in satoshi\\n /// @param _outputPKH the output pubkeyhash (hash160(recipient_pubkey))\\n /// @return the double-sha256 (hash256) signature hash as defined by bip143\\n function wpkhToWpkhSighash(\\n bytes memory _outpoint, // 36-byte UTXO id\\n bytes20 _inputPKH, // 20-byte hash160\\n bytes8 _inputValue, // 8-byte LE\\n bytes8 _outputValue, // 8-byte LE\\n bytes20 _outputPKH // 20-byte hash160\\n ) internal view returns (bytes32) {\\n return wpkhSpendSighash(\\n _outpoint,\\n _inputPKH,\\n _inputValue,\\n _outputValue,\\n abi.encodePacked(\\n hex\\\"160014\\\", // wpkh tag\\n _outputPKH)\\n );\\n }\\n\\n /// @notice Preserved for API compatibility with older version\\n /// @dev documented in bip143. many values are hardcoded here\\n /// @param _outpoint the bitcoin UTXO id (32-byte txid + 4-byte output index)\\n /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey))\\n /// @param _inputValue the value of the input in satoshi\\n /// @param _outputValue the value of the output in satoshi\\n /// @param _outputPKH the output pubkeyhash (hash160(recipient_pubkey))\\n /// @return the double-sha256 (hash256) signature hash as defined by bip143\\n function oneInputOneOutputSighash(\\n bytes memory _outpoint, // 36-byte UTXO id\\n bytes20 _inputPKH, // 20-byte hash160\\n bytes8 _inputValue, // 8-byte LE\\n bytes8 _outputValue, // 8-byte LE\\n bytes20 _outputPKH // 20-byte hash160\\n ) internal view returns (bytes32) {\\n return wpkhToWpkhSighash(_outpoint, _inputPKH, _inputValue, _outputValue, _outputPKH);\\n }\\n\\n}\\n\",\"keccak256\":\"0xfffbd5486af77058fe9385d63d433da914a043994b1affdfcb87248aa10a234c\"},\"@keep-network/bitcoin-spv-sol/contracts/SafeMath.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/*\\nThe MIT License (MIT)\\n\\nCopyright (c) 2016 Smart Contract Solutions, Inc.\\n\\nPermission is hereby granted, free of charge, to any person obtaining\\na copy of this software and associated documentation files (the\\n\\\"Software\\\"), to deal in the Software without restriction, including\\nwithout limitation the rights to use, copy, modify, merge, publish,\\ndistribute, sublicense, and/or sell copies of the Software, and to\\npermit persons to whom the Software is furnished to do so, subject to\\nthe following conditions:\\n\\nThe above copyright notice and this permission notice shall be included\\nin all copies or substantial portions of the Software.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\n*/\\n\\n\\n/**\\n * @title SafeMath\\n * @dev Math operations with safety checks that throw on error\\n */\\nlibrary SafeMath {\\n\\n /**\\n * @dev Multiplies two numbers, throws on overflow.\\n */\\n function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {\\n // Gas optimization: this is cheaper than asserting 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522\\n if (_a == 0) {\\n return 0;\\n }\\n\\n c = _a * _b;\\n require(c / _a == _b, \\\"Overflow during multiplication.\\\");\\n return c;\\n }\\n\\n /**\\n * @dev Integer division of two numbers, truncating the quotient.\\n */\\n function div(uint256 _a, uint256 _b) internal pure returns (uint256) {\\n // assert(_b > 0); // Solidity automatically throws when dividing by 0\\n // uint256 c = _a / _b;\\n // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold\\n return _a / _b;\\n }\\n\\n /**\\n * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).\\n */\\n function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {\\n require(_b <= _a, \\\"Underflow during subtraction.\\\");\\n return _a - _b;\\n }\\n\\n /**\\n * @dev Adds two numbers, throws on overflow.\\n */\\n function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {\\n c = _a + _b;\\n require(c >= _a, \\\"Overflow during addition.\\\");\\n return c;\\n }\\n}\\n\",\"keccak256\":\"0x35930d982394c7ffde439b82e5e696c5b21a6f09699d44861dfe409ef64084a3\"},\"@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/** @title ValidateSPV*/\\n/** @author Summa (https://summa.one) */\\n\\nimport {BytesLib} from \\\"./BytesLib.sol\\\";\\nimport {SafeMath} from \\\"./SafeMath.sol\\\";\\nimport {BTCUtils} from \\\"./BTCUtils.sol\\\";\\n\\n\\nlibrary ValidateSPV {\\n\\n using BTCUtils for bytes;\\n using BTCUtils for uint256;\\n using BytesLib for bytes;\\n using SafeMath for uint256;\\n\\n enum InputTypes { NONE, LEGACY, COMPATIBILITY, WITNESS }\\n enum OutputTypes { NONE, WPKH, WSH, OP_RETURN, PKH, SH, NONSTANDARD }\\n\\n uint256 constant ERR_BAD_LENGTH = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\\n uint256 constant ERR_INVALID_CHAIN = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe;\\n uint256 constant ERR_LOW_WORK = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd;\\n\\n function getErrBadLength() internal pure returns (uint256) {\\n return ERR_BAD_LENGTH;\\n }\\n\\n function getErrInvalidChain() internal pure returns (uint256) {\\n return ERR_INVALID_CHAIN;\\n }\\n\\n function getErrLowWork() internal pure returns (uint256) {\\n return ERR_LOW_WORK;\\n }\\n\\n /// @notice Validates a tx inclusion in the block\\n /// @dev `index` is not a reliable indicator of location within a block\\n /// @param _txid The txid (LE)\\n /// @param _merkleRoot The merkle root (as in the block header)\\n /// @param _intermediateNodes The proof's intermediate nodes (digests between leaf and root)\\n /// @param _index The leaf's index in the tree (0-indexed)\\n /// @return true if fully valid, false otherwise\\n function prove(\\n bytes32 _txid,\\n bytes32 _merkleRoot,\\n bytes memory _intermediateNodes,\\n uint _index\\n ) internal view returns (bool) {\\n // Shortcut the empty-block case\\n if (_txid == _merkleRoot && _index == 0 && _intermediateNodes.length == 0) {\\n return true;\\n }\\n\\n // If the Merkle proof failed, bubble up error\\n return BTCUtils.verifyHash256Merkle(\\n _txid,\\n _intermediateNodes,\\n _merkleRoot,\\n _index\\n );\\n }\\n\\n /// @notice Hashes transaction to get txid\\n /// @dev Supports Legacy and Witness\\n /// @param _version 4-bytes version\\n /// @param _vin Raw bytes length-prefixed input vector\\n /// @param _vout Raw bytes length-prefixed output vector\\n /// @param _locktime 4-byte tx locktime\\n /// @return 32-byte transaction id, little endian\\n function calculateTxId(\\n bytes4 _version,\\n bytes memory _vin,\\n bytes memory _vout,\\n bytes4 _locktime\\n ) internal view returns (bytes32) {\\n // Get transaction hash double-Sha256(version + nIns + inputs + nOuts + outputs + locktime)\\n return abi.encodePacked(_version, _vin, _vout, _locktime).hash256View();\\n }\\n\\n /// @notice Checks validity of header chain\\n /// @notice Compares the hash of each header to the prevHash in the next header\\n /// @param headers Raw byte array of header chain\\n /// @return totalDifficulty The total accumulated difficulty of the header chain, or an error code\\n function validateHeaderChain(\\n bytes memory headers\\n ) internal view returns (uint256 totalDifficulty) {\\n\\n // Check header chain length\\n if (headers.length % 80 != 0) {return ERR_BAD_LENGTH;}\\n\\n // Initialize header start index\\n bytes32 digest;\\n\\n totalDifficulty = 0;\\n\\n for (uint256 start = 0; start < headers.length; start += 80) {\\n\\n // After the first header, check that headers are in a chain\\n if (start != 0) {\\n if (!validateHeaderPrevHash(headers, start, digest)) {return ERR_INVALID_CHAIN;}\\n }\\n\\n // ith header target\\n uint256 target = headers.extractTargetAt(start);\\n\\n // Require that the header has sufficient work\\n digest = headers.hash256Slice(start, 80);\\n if(uint256(digest).reverseUint256() > target) {\\n return ERR_LOW_WORK;\\n }\\n\\n // Add ith header difficulty to difficulty sum\\n totalDifficulty = totalDifficulty + target.calculateDifficulty();\\n }\\n }\\n\\n /// @notice Checks validity of header work\\n /// @param digest Header digest\\n /// @param target The target threshold\\n /// @return true if header work is valid, false otherwise\\n function validateHeaderWork(\\n bytes32 digest,\\n uint256 target\\n ) internal pure returns (bool) {\\n if (digest == bytes32(0)) {return false;}\\n return (uint256(digest).reverseUint256() < target);\\n }\\n\\n /// @notice Checks validity of header chain\\n /// @dev Compares current header prevHash to previous header's digest\\n /// @param headers The raw bytes array containing the header\\n /// @param at The position of the header\\n /// @param prevHeaderDigest The previous header's digest\\n /// @return true if the connect is valid, false otherwise\\n function validateHeaderPrevHash(\\n bytes memory headers,\\n uint256 at,\\n bytes32 prevHeaderDigest\\n ) internal pure returns (bool) {\\n\\n // Extract prevHash of current header\\n bytes32 prevHash = headers.extractPrevBlockLEAt(at);\\n\\n // Compare prevHash of current header to previous header's digest\\n if (prevHash != prevHeaderDigest) {return false;}\\n\\n return true;\\n }\\n}\\n\",\"keccak256\":\"0xce3febbf3ad3a7ff8a8effd0c7ccaf7ccfa2719578b537d49ea196f0bae8062b\"},\"@keep-network/ecdsa/contracts/EcdsaDkgValidator.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\n// Initial version copied from Keep Network Random Beacon:\\n// https://github.com/keep-network/keep-core/blob/5138c7628868dbeed3ae2164f76fccc6c1fbb9e8/solidity/random-beacon/contracts/DKGValidator.sol\\n//\\n// With the following differences:\\n// - group public key length,\\n// - group size and related thresholds,\\n// - documentation.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/libraries/BytesLib.sol\\\";\\nimport \\\"@keep-network/sortition-pools/contracts/SortitionPool.sol\\\";\\nimport \\\"./libraries/EcdsaDkg.sol\\\";\\n\\n/// @title DKG result validator\\n/// @notice EcdsaDkgValidator allows performing a full validation of DKG result,\\n/// including checking the format of fields in the result, declared\\n/// selected group members, and signatures of operators supporting the\\n/// result. The operator submitting the result should perform the\\n/// validation using a free contract call before submitting the result\\n/// to ensure their result is valid and can not be challenged. All other\\n/// network operators should perform validation of the submitted result\\n/// using a free contract call and challenge the result if the\\n/// validation fails.\\ncontract EcdsaDkgValidator {\\n using BytesLib for bytes;\\n using ECDSA for bytes32;\\n\\n /// @dev Size of a group in DKG.\\n uint256 public constant groupSize = 100;\\n\\n /// @dev The minimum number of group members needed to interact according to\\n /// the protocol to produce a signature. The adversary can not learn\\n /// anything about the key as long as it does not break into\\n /// groupThreshold+1 of members.\\n uint256 public constant groupThreshold = 51;\\n\\n /// @dev The minimum number of active and properly behaving group members\\n /// during the DKG needed to accept the result. This number is higher\\n /// than `groupThreshold` to keep a safety margin for members becoming\\n /// inactive after DKG so that the group can still produce signature.\\n uint256 public constant activeThreshold = 90; // 90% of groupSize\\n\\n /// @dev Size in bytes of a public key produced by group members during the\\n /// the DKG. The length assumes uncompressed ECDSA public key.\\n uint256 public constant publicKeyByteSize = 64;\\n\\n /// @dev Size in bytes of a single signature produced by operator supporting\\n /// DKG result.\\n uint256 public constant signatureByteSize = 65;\\n\\n SortitionPool public immutable sortitionPool;\\n\\n constructor(SortitionPool _sortitionPool) {\\n sortitionPool = _sortitionPool;\\n }\\n\\n /// @notice Performs a full validation of DKG result, including checking the\\n /// format of fields in the result, declared selected group members,\\n /// and signatures of operators supporting the result.\\n /// @param seed seed used to start the DKG and select group members\\n /// @param startBlock DKG start block\\n /// @return isValid true if the result is valid, false otherwise\\n /// @return errorMsg validation error message; empty for a valid result\\n function validate(\\n EcdsaDkg.Result calldata result,\\n uint256 seed,\\n uint256 startBlock\\n ) external view returns (bool isValid, string memory errorMsg) {\\n (bool hasValidFields, string memory error) = validateFields(result);\\n if (!hasValidFields) {\\n return (false, error);\\n }\\n\\n if (!validateSignatures(result, startBlock)) {\\n return (false, \\\"Invalid signatures\\\");\\n }\\n\\n if (!validateGroupMembers(result, seed)) {\\n return (false, \\\"Invalid group members\\\");\\n }\\n\\n // At this point all group members and misbehaved members were verified\\n if (!validateMembersHash(result)) {\\n return (false, \\\"Invalid members hash\\\");\\n }\\n\\n return (true, \\\"\\\");\\n }\\n\\n /// @notice Performs a static validation of DKG result fields: lengths,\\n /// ranges, and order of arrays.\\n /// @return isValid true if the result is valid, false otherwise\\n /// @return errorMsg validation error message; empty for a valid result\\n function validateFields(EcdsaDkg.Result calldata result)\\n public\\n pure\\n returns (bool isValid, string memory errorMsg)\\n {\\n if (result.groupPubKey.length != publicKeyByteSize) {\\n return (false, \\\"Malformed group public key\\\");\\n }\\n\\n // The number of misbehaved members can not exceed the threshold.\\n // Misbehaved member indices needs to be unique, between [1, groupSize],\\n // and sorted in ascending order.\\n uint8[] calldata misbehavedMembersIndices = result\\n .misbehavedMembersIndices;\\n if (groupSize - misbehavedMembersIndices.length < activeThreshold) {\\n return (false, \\\"Too many members misbehaving during DKG\\\");\\n }\\n if (misbehavedMembersIndices.length > 1) {\\n if (\\n misbehavedMembersIndices[0] < 1 ||\\n misbehavedMembersIndices[misbehavedMembersIndices.length - 1] >\\n groupSize\\n ) {\\n return (false, \\\"Corrupted misbehaved members indices\\\");\\n }\\n for (uint256 i = 1; i < misbehavedMembersIndices.length; i++) {\\n if (\\n misbehavedMembersIndices[i - 1] >=\\n misbehavedMembersIndices[i]\\n ) {\\n return (false, \\\"Corrupted misbehaved members indices\\\");\\n }\\n }\\n }\\n\\n // Each signature needs to have a correct length and signatures need to\\n // be provided.\\n uint256 signaturesCount = result.signatures.length / signatureByteSize;\\n if (result.signatures.length == 0) {\\n return (false, \\\"No signatures provided\\\");\\n }\\n if (result.signatures.length % signatureByteSize != 0) {\\n return (false, \\\"Malformed signatures array\\\");\\n }\\n\\n // We expect the same amount of signatures as the number of declared\\n // group member indices that signed the result.\\n uint256[] calldata signingMembersIndices = result.signingMembersIndices;\\n if (signaturesCount != signingMembersIndices.length) {\\n return (false, \\\"Unexpected signatures count\\\");\\n }\\n if (signaturesCount < groupThreshold) {\\n return (false, \\\"Too few signatures\\\");\\n }\\n if (signaturesCount > groupSize) {\\n return (false, \\\"Too many signatures\\\");\\n }\\n\\n // Signing member indices needs to be unique, between [1,groupSize],\\n // and sorted in ascending order.\\n if (\\n signingMembersIndices[0] < 1 ||\\n signingMembersIndices[signingMembersIndices.length - 1] > groupSize\\n ) {\\n return (false, \\\"Corrupted signing member indices\\\");\\n }\\n for (uint256 i = 1; i < signingMembersIndices.length; i++) {\\n if (signingMembersIndices[i - 1] >= signingMembersIndices[i]) {\\n return (false, \\\"Corrupted signing member indices\\\");\\n }\\n }\\n\\n return (true, \\\"\\\");\\n }\\n\\n /// @notice Performs validation of group members as declared in DKG\\n /// result against group members selected by the sortition pool.\\n /// @param seed seed used to start the DKG and select group members\\n /// @return true if group members matches; false otherwise\\n function validateGroupMembers(EcdsaDkg.Result calldata result, uint256 seed)\\n public\\n view\\n returns (bool)\\n {\\n uint32[] calldata resultMembers = result.members;\\n uint32[] memory actualGroupMembers = sortitionPool.selectGroup(\\n groupSize,\\n bytes32(seed)\\n );\\n if (resultMembers.length != actualGroupMembers.length) {\\n return false;\\n }\\n for (uint256 i = 0; i < resultMembers.length; i++) {\\n if (resultMembers[i] != actualGroupMembers[i]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n /// @notice Performs validation of signatures supplied in DKG result.\\n /// Note that this function does not check if addresses which\\n /// supplied signatures supporting the result are the ones selected\\n /// to the group by sortition pool. This function should be used\\n /// together with `validateGroupMembers`.\\n /// @param startBlock DKG start block\\n /// @return true if group members matches; false otherwise\\n function validateSignatures(\\n EcdsaDkg.Result calldata result,\\n uint256 startBlock\\n ) public view returns (bool) {\\n bytes32 hash = keccak256(\\n abi.encode(\\n block.chainid,\\n result.groupPubKey,\\n result.misbehavedMembersIndices,\\n startBlock\\n )\\n ).toEthSignedMessageHash();\\n\\n uint256[] calldata signingMembersIndices = result.signingMembersIndices;\\n uint32[] memory signingMemberIds = new uint32[](\\n signingMembersIndices.length\\n );\\n for (uint256 i = 0; i < signingMembersIndices.length; i++) {\\n signingMemberIds[i] = result.members[signingMembersIndices[i] - 1];\\n }\\n\\n address[] memory signingMemberAddresses = sortitionPool.getIDOperators(\\n signingMemberIds\\n );\\n\\n bytes memory current; // Current signature to be checked.\\n\\n uint256 signaturesCount = result.signatures.length / signatureByteSize;\\n for (uint256 i = 0; i < signaturesCount; i++) {\\n current = result.signatures.slice(\\n signatureByteSize * i,\\n signatureByteSize\\n );\\n address recoveredAddress = hash.recover(current);\\n\\n if (signingMemberAddresses[i] != recoveredAddress) {\\n return false;\\n }\\n }\\n\\n return true;\\n }\\n\\n /// @notice Performs validation of hashed group members that actively took\\n /// part in DKG.\\n /// @param result DKG result\\n /// @return true if calculated result's group members hash matches with the\\n /// one that is challenged.\\n function validateMembersHash(EcdsaDkg.Result calldata result)\\n public\\n pure\\n returns (bool)\\n {\\n if (result.misbehavedMembersIndices.length > 0) {\\n // members that generated a group signing key\\n uint32[] memory groupMembers = new uint32[](\\n result.members.length - result.misbehavedMembersIndices.length\\n );\\n uint256 k = 0; // misbehaved members counter\\n uint256 j = 0; // group members counter\\n for (uint256 i = 0; i < result.members.length; i++) {\\n // misbehaved member indices start from 1, so we need to -1 on misbehaved\\n if (i != result.misbehavedMembersIndices[k] - 1) {\\n groupMembers[j] = result.members[i];\\n j++;\\n } else if (k < result.misbehavedMembersIndices.length - 1) {\\n k++;\\n }\\n }\\n\\n return keccak256(abi.encode(groupMembers)) == result.membersHash;\\n }\\n\\n return keccak256(abi.encode(result.members)) == result.membersHash;\\n }\\n}\\n\",\"keccak256\":\"0xe8f3d63ef4213ac71d447726be3971c5ed6b0b0eb145763d324faecdce707bf6\",\"license\":\"GPL-3.0-only\"},\"@keep-network/ecdsa/contracts/api/IWalletOwner.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\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\",\"keccak256\":\"0x8d4654269ab20710e8a729c225c2c69edae7f01ddbd5e037ab591df65e32faa8\",\"license\":\"GPL-3.0-only\"},\"@keep-network/ecdsa/contracts/api/IWalletRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"../libraries/EcdsaDkg.sol\\\";\\n\\ninterface IWalletRegistry {\\n /// @notice Requests a new wallet creation.\\n /// @dev Only the Wallet Owner can call this function.\\n function requestNewWallet() external;\\n\\n /// @notice Closes an existing wallet.\\n /// @param walletID ID of the wallet.\\n /// @dev Only the Wallet Owner can call this function.\\n function closeWallet(bytes32 walletID) external;\\n\\n /// @notice Adds all signing group members of the wallet with the given ID\\n /// to the slashing queue of the staking contract. The notifier will\\n /// receive reward per each group member from the staking contract\\n /// notifiers treasury. The reward is scaled by the\\n /// `rewardMultiplier` provided as a parameter.\\n /// @param amount Amount of tokens to seize from each signing group member\\n /// @param rewardMultiplier Fraction of the staking contract notifiers\\n /// reward the notifier should receive; should be between [0, 100]\\n /// @param notifier Address of the misbehavior notifier\\n /// @param walletID ID of the wallet\\n /// @param walletMembersIDs Identifiers of the wallet signing group members\\n /// @dev Only the Wallet Owner can call this function.\\n /// Requirements:\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events.\\n /// - `rewardMultiplier` must be between [0, 100].\\n /// - This function does revert if staking contract call reverts.\\n /// The calling code needs to handle the potential revert.\\n function seize(\\n uint96 amount,\\n uint256 rewardMultiplier,\\n address notifier,\\n bytes32 walletID,\\n uint32[] calldata walletMembersIDs\\n ) external;\\n\\n /// @notice Gets public key of a wallet with a given wallet ID.\\n /// The public key is returned in an uncompressed format as a 64-byte\\n /// concatenation of X and Y coordinates.\\n /// @param walletID ID of the wallet.\\n /// @return Uncompressed public key of the wallet.\\n function getWalletPublicKey(bytes32 walletID)\\n external\\n view\\n returns (bytes memory);\\n\\n /// @notice Check current wallet creation state.\\n function getWalletCreationState() external view returns (EcdsaDkg.State);\\n\\n /// @notice Checks whether the given operator is a member of the given\\n /// wallet signing group.\\n /// @param walletID ID of the wallet\\n /// @param walletMembersIDs Identifiers of the wallet signing group members\\n /// @param operator Address of the checked operator\\n /// @param walletMemberIndex Position of the operator in the wallet signing\\n /// group members list\\n /// @return True - if the operator is a member of the given wallet signing\\n /// group. False - otherwise.\\n /// @dev Requirements:\\n /// - The `operator` parameter must be an actual sortition pool operator.\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events.\\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]\\n function isWalletMember(\\n bytes32 walletID,\\n uint32[] calldata walletMembersIDs,\\n address operator,\\n uint256 walletMemberIndex\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xb97ece7c12601396fed705386a4e3337ee3a4809dca090a5acb62c2949337c68\",\"license\":\"GPL-3.0-only\"},\"@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n//\\n\\n// Initial version copied from Keep Network Random Beacon:\\n// https://github.com/keep-network/keep-core/blob/5138c7628868dbeed3ae2164f76fccc6c1fbb9e8/solidity/random-beacon/contracts/libraries/DKG.sol\\n//\\n// With the following differences:\\n// - the group size was set to 100,\\n// - offchainDkgTimeout was removed,\\n// - submission eligibility verification is not performed on-chain,\\n// - submission eligibility delay was replaced with a submission timeout,\\n// - seed timeout notification requires seedTimeout period to pass.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol\\\";\\nimport \\\"@keep-network/sortition-pools/contracts/SortitionPool.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/libraries/BytesLib.sol\\\";\\nimport \\\"../EcdsaDkgValidator.sol\\\";\\n\\nlibrary EcdsaDkg {\\n using BytesLib for bytes;\\n using ECDSAUpgradeable for bytes32;\\n\\n struct Parameters {\\n // Time in blocks during which a seed is expected to be delivered.\\n // DKG starts only after a seed is delivered. The time the contract\\n // awaits for a seed is not included in the DKG timeout.\\n uint256 seedTimeout;\\n // Time in blocks during which a submitted result can be challenged.\\n uint256 resultChallengePeriodLength;\\n // Extra gas required to be left at the end of the challenge DKG result\\n // transaction.\\n uint256 resultChallengeExtraGas;\\n // Time in blocks during which a result is expected to be submitted.\\n uint256 resultSubmissionTimeout;\\n // Time in blocks during which only the result submitter is allowed to\\n // approve it. Once this period ends and the submitter have not approved\\n // the result, anyone can do it.\\n uint256 submitterPrecedencePeriodLength;\\n // This struct doesn't contain `__gap` property as the structure is\\n // stored inside `Data` struct, that already have a gap that can be used\\n // on upgrade.\\n }\\n\\n struct Data {\\n // Address of the Sortition Pool contract.\\n SortitionPool sortitionPool;\\n // Address of the EcdsaDkgValidator contract.\\n EcdsaDkgValidator dkgValidator;\\n // DKG parameters. The parameters should persist between DKG executions.\\n // They should be updated with dedicated set functions only when DKG is not\\n // in progress.\\n Parameters parameters;\\n // Time in block at which DKG state was locked.\\n uint256 stateLockBlock;\\n // Time in blocks at which DKG started.\\n uint256 startBlock;\\n // Seed used to start DKG.\\n uint256 seed;\\n // Time in blocks that should be added to result submission eligibility\\n // delay calculation. It is used in case of a challenge to adjust\\n // DKG timeout calculation.\\n uint256 resultSubmissionStartBlockOffset;\\n // Hash of submitted DKG result.\\n bytes32 submittedResultHash;\\n // Block number from the moment of the DKG result submission.\\n uint256 submittedResultBlock;\\n // Reserved storage space in case we need to add more variables.\\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n // slither-disable-next-line unused-state\\n uint256[38] __gap;\\n }\\n\\n /// @notice DKG result.\\n struct Result {\\n // Claimed submitter candidate group member index.\\n // Must be in range [1, groupSize].\\n uint256 submitterMemberIndex;\\n // Generated candidate group public key\\n bytes groupPubKey;\\n // Array of misbehaved members indices (disqualified or inactive).\\n // Indices must be in range [1, groupSize], unique, and sorted in ascending\\n // order.\\n uint8[] misbehavedMembersIndices;\\n // Concatenation of signatures from members supporting the result.\\n // The message to be signed by each member is keccak256 hash of the\\n // calculated group public key, misbehaved members indices and DKG\\n // start block. The calculated hash should be prefixed with prefixed with\\n // `\\\\x19Ethereum signed message:\\\\n` before signing, so the message to\\n // sign is:\\n // `\\\\x19Ethereum signed message:\\\\n${keccak256(\\n // groupPubKey, misbehavedMembersIndices, dkgStartBlock\\n // )}`\\n bytes signatures;\\n // Indices of members corresponding to each signature. Indices must be\\n // be in range [1, groupSize], unique, and sorted in ascending order.\\n uint256[] signingMembersIndices;\\n // Identifiers of candidate group members as outputted by the group\\n // selection protocol.\\n uint32[] members;\\n // Keccak256 hash of group members identifiers that actively took part\\n // in DKG (excluding IA/DQ members).\\n bytes32 membersHash;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\n }\\n\\n /// @notice States for phases of group creation. The states doesn't include\\n /// timeouts which should be tracked and notified individually.\\n enum State {\\n // Group creation is not in progress. It is a state set after group creation\\n // completion either by timeout or by a result approval.\\n IDLE,\\n // Group creation is awaiting the seed and sortition pool is locked.\\n AWAITING_SEED,\\n // DKG protocol execution is in progress. A result is being calculated\\n // by the clients in this state and the contract awaits a result submission.\\n // This is a state to which group creation returns in case of a result\\n // challenge notification.\\n AWAITING_RESULT,\\n // DKG result was submitted and awaits an approval or a challenge. If a result\\n // gets challenge the state returns to `AWAITING_RESULT`. If a result gets\\n // approval the state changes to `IDLE`.\\n CHALLENGE\\n }\\n\\n /// @dev Size of a group in ECDSA wallet.\\n uint256 public constant groupSize = 100;\\n\\n event DkgStarted(uint256 indexed seed);\\n\\n // To recreate the members that actively took part in dkg, the selected members\\n // array should be filtered out from misbehavedMembersIndices.\\n event DkgResultSubmitted(\\n bytes32 indexed resultHash,\\n uint256 indexed seed,\\n Result result\\n );\\n\\n event DkgTimedOut();\\n\\n event DkgResultApproved(\\n bytes32 indexed resultHash,\\n address indexed approver\\n );\\n\\n event DkgResultChallenged(\\n bytes32 indexed resultHash,\\n address indexed challenger,\\n string reason\\n );\\n\\n event DkgStateLocked();\\n\\n event DkgSeedTimedOut();\\n\\n /// @notice Initializes SortitionPool and EcdsaDkgValidator addresses.\\n /// Can be performed only once.\\n /// @param _sortitionPool Sortition Pool reference\\n /// @param _dkgValidator EcdsaDkgValidator reference\\n function init(\\n Data storage self,\\n SortitionPool _sortitionPool,\\n EcdsaDkgValidator _dkgValidator\\n ) internal {\\n require(\\n address(self.sortitionPool) == address(0),\\n \\\"Sortition Pool address already set\\\"\\n );\\n\\n require(\\n address(self.dkgValidator) == address(0),\\n \\\"DKG Validator address already set\\\"\\n );\\n\\n self.sortitionPool = _sortitionPool;\\n self.dkgValidator = _dkgValidator;\\n }\\n\\n /// @notice Determines the current state of group creation. It doesn't take\\n /// timeouts into consideration. The timeouts should be tracked and\\n /// notified separately.\\n function currentState(Data storage self)\\n internal\\n view\\n returns (State state)\\n {\\n state = State.IDLE;\\n\\n if (self.sortitionPool.isLocked()) {\\n state = State.AWAITING_SEED;\\n\\n if (self.startBlock > 0) {\\n state = State.AWAITING_RESULT;\\n\\n if (self.submittedResultBlock > 0) {\\n state = State.CHALLENGE;\\n }\\n }\\n }\\n }\\n\\n /// @notice Locks the sortition pool and starts awaiting for the\\n /// group creation seed.\\n function lockState(Data storage self) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n emit DkgStateLocked();\\n\\n self.sortitionPool.lock();\\n\\n self.stateLockBlock = block.number;\\n }\\n\\n function start(Data storage self, uint256 seed) internal {\\n require(\\n currentState(self) == State.AWAITING_SEED,\\n \\\"Current state is not AWAITING_SEED\\\"\\n );\\n\\n emit DkgStarted(seed);\\n\\n self.startBlock = block.number;\\n self.seed = seed;\\n }\\n\\n /// @notice Allows to submit a DKG result. The submitted result does not go\\n /// through a validation and before it gets accepted, it needs to\\n /// wait through the challenge period during which everyone has\\n /// a chance to challenge the result as invalid one. Submitter of\\n /// the result needs to be in the sortition pool and if the result\\n /// gets challenged, the submitter will get slashed.\\n function submitResult(Data storage self, Result calldata result) internal {\\n require(\\n currentState(self) == State.AWAITING_RESULT,\\n \\\"Current state is not AWAITING_RESULT\\\"\\n );\\n require(!hasDkgTimedOut(self), \\\"DKG timeout already passed\\\");\\n\\n SortitionPool sortitionPool = self.sortitionPool;\\n\\n // Submitter must be an operator in the sortition pool.\\n // Declared submitter's member index in the DKG result needs to match\\n // the address calling this function.\\n require(\\n sortitionPool.isOperatorInPool(msg.sender),\\n \\\"Submitter not in the sortition pool\\\"\\n );\\n require(\\n sortitionPool.getIDOperator(\\n result.members[result.submitterMemberIndex - 1]\\n ) == msg.sender,\\n \\\"Unexpected submitter index\\\"\\n );\\n\\n self.submittedResultHash = keccak256(abi.encode(result));\\n self.submittedResultBlock = block.number;\\n\\n emit DkgResultSubmitted(self.submittedResultHash, self.seed, result);\\n }\\n\\n /// @notice Checks if awaiting seed timed out.\\n /// @return True if awaiting seed timed out, false otherwise.\\n function hasSeedTimedOut(Data storage self) internal view returns (bool) {\\n return\\n currentState(self) == State.AWAITING_SEED &&\\n block.number > (self.stateLockBlock + self.parameters.seedTimeout);\\n }\\n\\n /// @notice Checks if DKG timed out. The DKG timeout period includes time required\\n /// for off-chain protocol execution and time for the result publication.\\n /// After this time a result cannot be submitted and DKG can be notified\\n /// about the timeout. DKG period is adjusted by result submission\\n /// offset that include blocks that were mined while invalid result\\n /// has been registered until it got challenged.\\n /// @return True if DKG timed out, false otherwise.\\n function hasDkgTimedOut(Data storage self) internal view returns (bool) {\\n return\\n currentState(self) == State.AWAITING_RESULT &&\\n block.number >\\n (self.startBlock +\\n self.resultSubmissionStartBlockOffset +\\n self.parameters.resultSubmissionTimeout);\\n }\\n\\n /// @notice Notifies about the seed was not delivered and restores the\\n /// initial DKG state (IDLE).\\n function notifySeedTimeout(Data storage self) internal {\\n require(hasSeedTimedOut(self), \\\"Awaiting seed has not timed out\\\");\\n\\n emit DkgSeedTimedOut();\\n\\n complete(self);\\n }\\n\\n /// @notice Notifies about DKG timeout.\\n function notifyDkgTimeout(Data storage self) internal {\\n require(hasDkgTimedOut(self), \\\"DKG has not timed out\\\");\\n\\n emit DkgTimedOut();\\n\\n complete(self);\\n }\\n\\n /// @notice Approves DKG result. Can be called when the challenge period for\\n /// the submitted result is finished. Considers the submitted result\\n /// as valid. For the first `submitterPrecedencePeriodLength`\\n /// blocks after the end of the challenge period can be called only\\n /// by the DKG result submitter. After that time, can be called by\\n /// anyone.\\n /// @dev Can be called after a challenge period for the submitted result.\\n /// @param result Result to approve. Must match the submitted result stored\\n /// during `submitResult`.\\n /// @return misbehavedMembers Identifiers of members who misbehaved during DKG.\\n function approveResult(Data storage self, Result calldata result)\\n internal\\n returns (uint32[] memory misbehavedMembers)\\n {\\n require(\\n currentState(self) == State.CHALLENGE,\\n \\\"Current state is not CHALLENGE\\\"\\n );\\n\\n uint256 challengePeriodEnd = self.submittedResultBlock +\\n self.parameters.resultChallengePeriodLength;\\n\\n require(\\n block.number > challengePeriodEnd,\\n \\\"Challenge period has not passed yet\\\"\\n );\\n\\n require(\\n keccak256(abi.encode(result)) == self.submittedResultHash,\\n \\\"Result under approval is different than the submitted one\\\"\\n );\\n\\n // Extract submitter member address. Submitter member index is in\\n // range [1, groupSize] so we need to -1 when fetching identifier from members\\n // array.\\n address submitterMember = self.sortitionPool.getIDOperator(\\n result.members[result.submitterMemberIndex - 1]\\n );\\n\\n require(\\n msg.sender == submitterMember ||\\n block.number >\\n challengePeriodEnd +\\n self.parameters.submitterPrecedencePeriodLength,\\n \\\"Only the DKG result submitter can approve the result at this moment\\\"\\n );\\n\\n // Extract misbehaved members identifiers. Misbehaved members indices\\n // are in range [1, groupSize], so we need to -1 when fetching identifiers from\\n // members array.\\n misbehavedMembers = new uint32[](\\n result.misbehavedMembersIndices.length\\n );\\n for (uint256 i = 0; i < result.misbehavedMembersIndices.length; i++) {\\n misbehavedMembers[i] = result.members[\\n result.misbehavedMembersIndices[i] - 1\\n ];\\n }\\n\\n emit DkgResultApproved(self.submittedResultHash, msg.sender);\\n\\n return misbehavedMembers;\\n }\\n\\n /// @notice Challenges DKG result. If the submitted result is proved to be\\n /// invalid it reverts the DKG back to the result submission phase.\\n /// @dev Can be called during a challenge period for the submitted result.\\n /// @param result Result to challenge. Must match the submitted result\\n /// stored during `submitResult`.\\n /// @return maliciousResultHash Hash of the malicious result.\\n /// @return maliciousSubmitter Identifier of the malicious submitter.\\n function challengeResult(Data storage self, Result calldata result)\\n internal\\n returns (bytes32 maliciousResultHash, uint32 maliciousSubmitter)\\n {\\n require(\\n currentState(self) == State.CHALLENGE,\\n \\\"Current state is not CHALLENGE\\\"\\n );\\n\\n require(\\n block.number <=\\n self.submittedResultBlock +\\n self.parameters.resultChallengePeriodLength,\\n \\\"Challenge period has already passed\\\"\\n );\\n\\n require(\\n keccak256(abi.encode(result)) == self.submittedResultHash,\\n \\\"Result under challenge is different than the submitted one\\\"\\n );\\n\\n // https://github.com/crytic/slither/issues/982\\n // slither-disable-next-line unused-return\\n try\\n self.dkgValidator.validate(result, self.seed, self.startBlock)\\n returns (\\n // slither-disable-next-line uninitialized-local,variable-scope\\n bool isValid,\\n // slither-disable-next-line uninitialized-local,variable-scope\\n string memory errorMsg\\n ) {\\n if (isValid) {\\n revert(\\\"unjustified challenge\\\");\\n }\\n\\n emit DkgResultChallenged(\\n self.submittedResultHash,\\n msg.sender,\\n errorMsg\\n );\\n } catch {\\n // if the validation reverted we consider the DKG result as invalid\\n emit DkgResultChallenged(\\n self.submittedResultHash,\\n msg.sender,\\n \\\"validation reverted\\\"\\n );\\n }\\n\\n // Consider result hash as malicious.\\n maliciousResultHash = self.submittedResultHash;\\n maliciousSubmitter = result.members[result.submitterMemberIndex - 1];\\n\\n // Adjust DKG result submission block start, so submission stage starts\\n // from the beginning.\\n self.resultSubmissionStartBlockOffset = block.number - self.startBlock;\\n\\n submittedResultCleanup(self);\\n\\n return (maliciousResultHash, maliciousSubmitter);\\n }\\n\\n /// @notice Due to EIP150, 1/64 of the gas is not forwarded to the call, and\\n /// will be kept to execute the remaining operations in the function\\n /// after the call inside the try-catch.\\n ///\\n /// To ensure there is no way for the caller to manipulate gas limit\\n /// in such a way that the call inside try-catch fails with out-of-gas\\n /// and the rest of the function is executed with the remaining\\n /// 1/64 of gas, we require an extra gas amount to be left at the\\n /// end of the call to the function challenging DKG result and\\n /// wrapping the call to EcdsaDkgValidator and TokenStaking\\n /// contracts inside a try-catch.\\n function requireChallengeExtraGas(Data storage self) internal view {\\n require(\\n gasleft() >= self.parameters.resultChallengeExtraGas,\\n \\\"Not enough extra gas left\\\"\\n );\\n }\\n\\n /// @notice Checks if DKG result is valid for the current DKG.\\n /// @param result DKG result.\\n /// @return True if the result is valid. If the result is invalid it returns\\n /// false and an error message.\\n function isResultValid(Data storage self, Result calldata result)\\n internal\\n view\\n returns (bool, string memory)\\n {\\n require(self.startBlock > 0, \\\"DKG has not been started\\\");\\n\\n return self.dkgValidator.validate(result, self.seed, self.startBlock);\\n }\\n\\n /// @notice Set setSeedTimeout parameter.\\n function setSeedTimeout(Data storage self, uint256 newSeedTimeout)\\n internal\\n {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(newSeedTimeout > 0, \\\"New value should be greater than zero\\\");\\n\\n self.parameters.seedTimeout = newSeedTimeout;\\n }\\n\\n /// @notice Set resultChallengePeriodLength parameter.\\n function setResultChallengePeriodLength(\\n Data storage self,\\n uint256 newResultChallengePeriodLength\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(\\n newResultChallengePeriodLength > 0,\\n \\\"New value should be greater than zero\\\"\\n );\\n\\n self\\n .parameters\\n .resultChallengePeriodLength = newResultChallengePeriodLength;\\n }\\n\\n /// @notice Set resultChallengeExtraGas parameter.\\n function setResultChallengeExtraGas(\\n Data storage self,\\n uint256 newResultChallengeExtraGas\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n self.parameters.resultChallengeExtraGas = newResultChallengeExtraGas;\\n }\\n\\n /// @notice Set resultSubmissionTimeout parameter.\\n function setResultSubmissionTimeout(\\n Data storage self,\\n uint256 newResultSubmissionTimeout\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(\\n newResultSubmissionTimeout > 0,\\n \\\"New value should be greater than zero\\\"\\n );\\n\\n self.parameters.resultSubmissionTimeout = newResultSubmissionTimeout;\\n }\\n\\n /// @notice Set submitterPrecedencePeriodLength parameter.\\n function setSubmitterPrecedencePeriodLength(\\n Data storage self,\\n uint256 newSubmitterPrecedencePeriodLength\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(\\n newSubmitterPrecedencePeriodLength <\\n self.parameters.resultSubmissionTimeout,\\n \\\"New value should be less than result submission period length\\\"\\n );\\n\\n self\\n .parameters\\n .submitterPrecedencePeriodLength = newSubmitterPrecedencePeriodLength;\\n }\\n\\n /// @notice Completes DKG by cleaning up state.\\n /// @dev Should be called after DKG times out or a result is approved.\\n function complete(Data storage self) internal {\\n delete self.startBlock;\\n delete self.seed;\\n delete self.resultSubmissionStartBlockOffset;\\n submittedResultCleanup(self);\\n self.sortitionPool.unlock();\\n }\\n\\n /// @notice Cleans up submitted result state either after DKG completion\\n /// (as part of `complete` method) or after justified challenge.\\n function submittedResultCleanup(Data storage self) private {\\n delete self.submittedResultHash;\\n delete self.submittedResultBlock;\\n }\\n}\\n\",\"keccak256\":\"0xd6c442e1db2dc95730443fb6496d47889a69bed2fbace466b27f9727484c25ec\",\"license\":\"GPL-3.0-only\"},\"@keep-network/random-beacon/contracts/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\n\\n/// @notice Governable contract.\\n/// @dev A constructor is not defined, which makes the contract compatible with\\n/// upgradable proxies. This requires calling explicitly `_transferGovernance`\\n/// function in a child contract.\\nabstract contract Governable {\\n // Governance of the contract\\n // The variable should be initialized by the implementing contract.\\n // slither-disable-next-line uninitialized-state\\n address public governance;\\n\\n // Reserved storage space in case we need to add more variables,\\n // since there are upgradeable contracts that inherit from this one.\\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n // slither-disable-next-line unused-state\\n uint256[49] private __gap;\\n\\n event GovernanceTransferred(address oldGovernance, address newGovernance);\\n\\n modifier onlyGovernance() virtual {\\n require(governance == msg.sender, \\\"Caller is not the governance\\\");\\n _;\\n }\\n\\n /// @notice Transfers governance of the contract to `newGovernance`.\\n function transferGovernance(address newGovernance)\\n external\\n virtual\\n onlyGovernance\\n {\\n require(\\n newGovernance != address(0),\\n \\\"New governance is the zero address\\\"\\n );\\n _transferGovernance(newGovernance);\\n }\\n\\n function _transferGovernance(address newGovernance) internal virtual {\\n address oldGovernance = governance;\\n governance = newGovernance;\\n emit GovernanceTransferred(oldGovernance, newGovernance);\\n }\\n}\\n\",\"keccak256\":\"0xcc6a0fe8fdf05a805d2874dc7dd76dede1eb16e3ab77f2d0069dbb92272ab0a3\",\"license\":\"GPL-3.0-only\"},\"@keep-network/random-beacon/contracts/ReimbursementPool.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/security/ReentrancyGuard.sol\\\";\\n\\ncontract ReimbursementPool is Ownable, ReentrancyGuard {\\n /// @notice Authorized contracts that can interact with the reimbursment pool.\\n /// Authorization can be granted and removed by the owner.\\n mapping(address => bool) public isAuthorized;\\n\\n /// @notice Static gas includes:\\n /// - cost of the refund function\\n /// - base transaction cost\\n uint256 public staticGas;\\n\\n /// @notice Max gas price used to reimburse a transaction submitter. Protects\\n /// against malicious operator-miners.\\n uint256 public maxGasPrice;\\n\\n event StaticGasUpdated(uint256 newStaticGas);\\n\\n event MaxGasPriceUpdated(uint256 newMaxGasPrice);\\n\\n event SendingEtherFailed(uint256 refundAmount, address receiver);\\n\\n event AuthorizedContract(address thirdPartyContract);\\n\\n event UnauthorizedContract(address thirdPartyContract);\\n\\n event FundsWithdrawn(uint256 withdrawnAmount, address receiver);\\n\\n constructor(uint256 _staticGas, uint256 _maxGasPrice) {\\n staticGas = _staticGas;\\n maxGasPrice = _maxGasPrice;\\n }\\n\\n /// @notice Receive ETH\\n receive() external payable {}\\n\\n /// @notice Refunds ETH to a spender for executing specific transactions.\\n /// @dev Ignoring the result of sending ETH to a receiver is made on purpose.\\n /// For EOA receiving ETH should always work. If a receiver is a smart\\n /// contract, then we do not want to fail a transaction, because in some\\n /// cases the refund is done at the very end of multiple calls where all\\n /// the previous calls were already paid off. It is a receiver's smart\\n /// contract resposibility to make sure it can receive ETH.\\n /// @dev Only authorized contracts are allowed calling this function.\\n /// @param gasSpent Gas spent on a transaction that needs to be reimbursed.\\n /// @param receiver Address where the reimbursment is sent.\\n function refund(uint256 gasSpent, address receiver) external nonReentrant {\\n require(\\n isAuthorized[msg.sender],\\n \\\"Contract is not authorized for a refund\\\"\\n );\\n require(receiver != address(0), \\\"Receiver's address cannot be zero\\\");\\n\\n uint256 gasPrice = tx.gasprice < maxGasPrice\\n ? tx.gasprice\\n : maxGasPrice;\\n\\n uint256 refundAmount = (gasSpent + staticGas) * gasPrice;\\n\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,unchecked-lowlevel\\n (bool sent, ) = receiver.call{value: refundAmount}(\\\"\\\");\\n /* solhint-enable avoid-low-level-calls */\\n if (!sent) {\\n // slither-disable-next-line reentrancy-events\\n emit SendingEtherFailed(refundAmount, receiver);\\n }\\n }\\n\\n /// @notice Authorize a contract that can interact with this reimbursment pool.\\n /// Can be authorized by the owner only.\\n /// @param _contract Authorized contract.\\n function authorize(address _contract) external onlyOwner {\\n isAuthorized[_contract] = true;\\n\\n emit AuthorizedContract(_contract);\\n }\\n\\n /// @notice Unauthorize a contract that was previously authorized to interact\\n /// with this reimbursment pool. Can be unauthorized by the\\n /// owner only.\\n /// @param _contract Authorized contract.\\n function unauthorize(address _contract) external onlyOwner {\\n delete isAuthorized[_contract];\\n\\n emit UnauthorizedContract(_contract);\\n }\\n\\n /// @notice Setting a static gas cost for executing a transaction. Can be set\\n /// by the owner only.\\n /// @param _staticGas Static gas cost.\\n function setStaticGas(uint256 _staticGas) external onlyOwner {\\n staticGas = _staticGas;\\n\\n emit StaticGasUpdated(_staticGas);\\n }\\n\\n /// @notice Setting a max gas price for transactions. Can be set by the\\n /// owner only.\\n /// @param _maxGasPrice Max gas price used to reimburse tx submitters.\\n function setMaxGasPrice(uint256 _maxGasPrice) external onlyOwner {\\n maxGasPrice = _maxGasPrice;\\n\\n emit MaxGasPriceUpdated(_maxGasPrice);\\n }\\n\\n /// @notice Withdraws all ETH from this pool which are sent to a given\\n /// address. Can be set by the owner only.\\n /// @param receiver An address where ETH is sent.\\n function withdrawAll(address receiver) external onlyOwner {\\n withdraw(address(this).balance, receiver);\\n }\\n\\n /// @notice Withdraws ETH amount from this pool which are sent to a given\\n /// address. Can be set by the owner only.\\n /// @param amount Amount to withdraw from the pool.\\n /// @param receiver An address where ETH is sent.\\n function withdraw(uint256 amount, address receiver) public onlyOwner {\\n require(\\n address(this).balance >= amount,\\n \\\"Insufficient contract balance\\\"\\n );\\n require(receiver != address(0), \\\"Receiver's address cannot be zero\\\");\\n\\n emit FundsWithdrawn(amount, receiver);\\n\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,arbitrary-send\\n (bool sent, ) = receiver.call{value: amount}(\\\"\\\");\\n /* solhint-enable avoid-low-level-calls */\\n require(sent, \\\"Failed to send Ether\\\");\\n }\\n}\\n\",\"keccak256\":\"0xd6c24368cc4c6349b8b614e878ca961cad8254b8e8db1cc0abe452a70022ce50\",\"license\":\"GPL-3.0-only\"},\"@keep-network/random-beacon/contracts/libraries/BytesLib.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n//\\n\\npragma solidity 0.8.17;\\n\\n/*\\nVersion pulled from keep-core v1:\\nhttps://github.com/keep-network/keep-core/blob/f297202db00c027978ad8e7103a356503de5773c/solidity-v1/contracts/utils/BytesLib.sol\\n\\nTo compile it with solidity 0.8 `_preBytes_slot` was replaced with `_preBytes.slot`.\\n*/\\n\\n/*\\nhttps://github.com/GNSPS/solidity-bytes-utils/\\nThis is free and unencumbered software released into the public domain.\\nAnyone is free to copy, modify, publish, use, compile, sell, or\\ndistribute this software, either in source code form or as a compiled\\nbinary, for any purpose, commercial or non-commercial, and by any\\nmeans.\\nIn jurisdictions that recognize copyright laws, the author or authors\\nof this software dedicate any and all copyright interest in the\\nsoftware to the public domain. We make this dedication for the benefit\\nof the public at large and to the detriment of our heirs and\\nsuccessors. We intend this dedication to be an overt act of\\nrelinquishment in perpetuity of all present and future rights to this\\nsoftware under copyright law.\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND,\\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\\nOTHER DEALINGS IN THE SOFTWARE.\\nFor more information, please refer to \\n*/\\n\\n/** @title BytesLib **/\\n/** @author https://github.com/GNSPS **/\\n\\nlibrary BytesLib {\\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes)\\n internal\\n {\\n assembly {\\n // Read the first 32 bytes of _preBytes storage, which is the length\\n // of the array. (We don't need to use the offset into the slot\\n // because arrays use the entire slot.)\\n let fslot := sload(_preBytes.slot)\\n // Arrays of 31 bytes or less have an even value in their slot,\\n // while longer arrays have an odd value. The actual length is\\n // the slot divided by two for odd values, and the lowest order\\n // byte divided by two for even values.\\n // If the slot is even, bitwise and the slot with 255 and divide by\\n // two to get the length. If the slot is odd, bitwise and the slot\\n // with -1 and divide by two.\\n let slength := div(\\n and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),\\n 2\\n )\\n let mlength := mload(_postBytes)\\n let newlength := add(slength, mlength)\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n switch add(lt(slength, 32), lt(newlength, 32))\\n case 2 {\\n // Since the new array still fits in the slot, we just need to\\n // update the contents of the slot.\\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\\n sstore(\\n _preBytes.slot,\\n // all the modifications to the slot are inside this\\n // next block\\n add(\\n // we can just add to the slot contents because the\\n // bytes we want to change are the LSBs\\n fslot,\\n add(\\n mul(\\n div(\\n // load the bytes from memory\\n mload(add(_postBytes, 0x20)),\\n // zero all bytes to the right\\n exp(0x100, sub(32, mlength))\\n ),\\n // and now shift left the number of bytes to\\n // leave space for the length in the slot\\n exp(0x100, sub(32, newlength))\\n ),\\n // increase length by the double of the memory\\n // bytes length\\n mul(mlength, 2)\\n )\\n )\\n )\\n }\\n case 1 {\\n // The stored value fits in the slot, but the combined value\\n // will exceed it.\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // The contents of the _postBytes array start 32 bytes into\\n // the structure. Our first read should obtain the `submod`\\n // bytes that can fit into the unused space in the last word\\n // of the stored array. To get this, we read 32 bytes starting\\n // from `submod`, so the data we read overlaps with the array\\n // contents by `submod` bytes. Masking the lowest-order\\n // `submod` bytes allows us to add that value directly to the\\n // stored value.\\n\\n let submod := sub(32, slength)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(\\n sc,\\n add(\\n and(\\n fslot,\\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\\n ),\\n and(mload(mc), mask)\\n )\\n )\\n\\n for {\\n mc := add(mc, 0x20)\\n sc := add(sc, 1)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n default {\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n // Start copying to the last used word of the stored array.\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // Copy over the first `submod` bytes of the new data as in\\n // case 1 above.\\n let slengthmod := mod(slength, 32)\\n let submod := sub(32, slengthmod)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\\n\\n for {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n }\\n }\\n\\n function equalStorage(bytes storage _preBytes, bytes memory _postBytes)\\n internal\\n view\\n returns (bool)\\n {\\n bool success = true;\\n\\n assembly {\\n // we know _preBytes_offset is 0\\n let fslot := sload(_preBytes.slot)\\n // Decode the length of the stored array like in concatStorage().\\n let slength := div(\\n and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),\\n 2\\n )\\n let mlength := mload(_postBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(slength, mlength)\\n case 1 {\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n if iszero(iszero(slength)) {\\n switch lt(slength, 32)\\n case 1 {\\n // blank the last byte which is the length\\n fslot := mul(div(fslot, 0x100), 0x100)\\n\\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\\n // unsuccess:\\n success := 0\\n }\\n }\\n default {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := keccak256(0x0, 0x20)\\n\\n let mc := add(_postBytes, 0x20)\\n let end := add(mc, mlength)\\n\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n for {\\n\\n } eq(add(lt(mc, end), cb), 2) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n if iszero(eq(sload(sc), mload(mc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function concat(bytes memory _preBytes, bytes memory _postBytes)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n bytes memory tempBytes;\\n\\n assembly {\\n // Get a location of some free memory and store it in tempBytes as\\n // Solidity does for memory variables.\\n tempBytes := mload(0x40)\\n\\n // Store the length of the first bytes array at the beginning of\\n // the memory for tempBytes.\\n let length := mload(_preBytes)\\n mstore(tempBytes, length)\\n\\n // Maintain a memory counter for the current write location in the\\n // temp bytes array by adding the 32 bytes for the array length to\\n // the starting location.\\n let mc := add(tempBytes, 0x20)\\n // Stop copying when the memory counter reaches the length of the\\n // first bytes array.\\n let end := add(mc, length)\\n\\n for {\\n // Initialize a copy counter to the start of the _preBytes data,\\n // 32 bytes into its memory.\\n let cc := add(_preBytes, 0x20)\\n } lt(mc, end) {\\n // Increase both counters by 32 bytes each iteration.\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // Write the _preBytes data into the tempBytes memory 32 bytes\\n // at a time.\\n mstore(mc, mload(cc))\\n }\\n\\n // Add the length of _postBytes to the current length of tempBytes\\n // and store it as the new length in the first 32 bytes of the\\n // tempBytes memory.\\n length := mload(_postBytes)\\n mstore(tempBytes, add(length, mload(tempBytes)))\\n\\n // Move the memory counter back from a multiple of 0x20 to the\\n // actual end of the _preBytes data.\\n mc := end\\n // Stop copying when the memory counter reaches the new combined\\n // length of the arrays.\\n end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n } lt(mc, end) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n mstore(mc, mload(cc))\\n }\\n\\n // Update the free-memory pointer by padding our last write location\\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\\n // next 32 byte block, then round down to the nearest multiple of\\n // 32. If the sum of the length of the two arrays is zero then add\\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\\n mstore(\\n 0x40,\\n and(\\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\\n not(31) // Round down to the nearest 32 bytes.\\n )\\n )\\n }\\n\\n return tempBytes;\\n }\\n\\n function slice(\\n bytes memory _bytes,\\n uint256 _start,\\n uint256 _length\\n ) internal pure returns (bytes memory res) {\\n uint256 _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n // Alloc bytes array with additional 32 bytes afterspace and assign it's size\\n res := mload(0x40)\\n mstore(0x40, add(add(res, 64), _length))\\n mstore(res, _length)\\n\\n // Compute distance between source and destination pointers\\n let diff := sub(res, add(_bytes, _start))\\n\\n for {\\n let src := add(add(_bytes, 32), _start)\\n let end := add(src, _length)\\n } lt(src, end) {\\n src := add(src, 32)\\n } {\\n mstore(add(src, diff), mload(src))\\n }\\n }\\n }\\n\\n function toAddress(bytes memory _bytes, uint256 _start)\\n internal\\n pure\\n returns (address)\\n {\\n uint256 _totalLen = _start + 20;\\n require(\\n _totalLen > _start && _bytes.length >= _totalLen,\\n \\\"Address conversion out of bounds.\\\"\\n );\\n address tempAddress;\\n\\n assembly {\\n tempAddress := div(\\n mload(add(add(_bytes, 0x20), _start)),\\n 0x1000000000000000000000000\\n )\\n }\\n\\n return tempAddress;\\n }\\n\\n function toUint8(bytes memory _bytes, uint256 _start)\\n internal\\n pure\\n returns (uint8)\\n {\\n require(\\n _bytes.length >= (_start + 1),\\n \\\"Uint8 conversion out of bounds.\\\"\\n );\\n uint8 tempUint;\\n\\n assembly {\\n tempUint := mload(add(add(_bytes, 0x1), _start))\\n }\\n\\n return tempUint;\\n }\\n\\n function toUint(bytes memory _bytes, uint256 _start)\\n internal\\n pure\\n returns (uint256)\\n {\\n uint256 _totalLen = _start + 32;\\n require(\\n _totalLen > _start && _bytes.length >= _totalLen,\\n \\\"Uint conversion out of bounds.\\\"\\n );\\n uint256 tempUint;\\n\\n assembly {\\n tempUint := mload(add(add(_bytes, 0x20), _start))\\n }\\n\\n return tempUint;\\n }\\n\\n function equal(bytes memory _preBytes, bytes memory _postBytes)\\n internal\\n pure\\n returns (bool)\\n {\\n bool success = true;\\n\\n assembly {\\n let length := mload(_preBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(length, mload(_postBytes))\\n case 1 {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n let mc := add(_preBytes, 0x20)\\n let end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n } eq(add(lt(mc, end), cb), 2) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // if any of these checks fails then arrays are not equal\\n if iszero(eq(mload(mc), mload(cc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function toBytes32(bytes memory _source)\\n internal\\n pure\\n returns (bytes32 result)\\n {\\n if (_source.length == 0) {\\n return 0x0;\\n }\\n\\n assembly {\\n result := mload(add(_source, 32))\\n }\\n }\\n\\n function keccak256Slice(\\n bytes memory _bytes,\\n uint256 _start,\\n uint256 _length\\n ) internal pure returns (bytes32 result) {\\n uint256 _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n result := keccak256(add(add(_bytes, 32), _start), _length)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3b76e2fe36eb777440250dcf2ea7a689375e8af22f3cc33521095ff6954becdb\",\"license\":\"GPL-3.0-only\"},\"@keep-network/sortition-pools/contracts/Branch.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Constants.sol\\\";\\n\\n/// @notice The implicit 8-ary trees of the sortition pool\\n/// rely on packing 8 \\\"slots\\\" of 32-bit values into each uint256.\\n/// The Branch library permits efficient calculations on these slots.\\nlibrary Branch {\\n /// @notice Calculate the right shift required\\n /// to make the 32 least significant bits of an uint256\\n /// be the bits of the `position`th slot\\n /// when treating the uint256 as a uint32[8].\\n ///\\n /// @dev Not used for efficiency reasons,\\n /// but left to illustrate the meaning of a common pattern.\\n /// I wish solidity had macros, even C macros.\\n function slotShift(uint256 position) internal pure returns (uint256) {\\n unchecked {\\n return position * Constants.SLOT_WIDTH;\\n }\\n }\\n\\n /// @notice Return the `position`th slot of the `node`,\\n /// treating `node` as a uint32[32].\\n function getSlot(uint256 node, uint256 position)\\n internal\\n pure\\n returns (uint256)\\n {\\n unchecked {\\n uint256 shiftBits = position * Constants.SLOT_WIDTH;\\n // Doing a bitwise AND with `SLOT_MAX`\\n // clears all but the 32 least significant bits.\\n // Because of the right shift by `slotShift(position)` bits,\\n // those 32 bits contain the 32 bits in the `position`th slot of `node`.\\n return (node >> shiftBits) & Constants.SLOT_MAX;\\n }\\n }\\n\\n /// @notice Return `node` with the `position`th slot set to zero.\\n function clearSlot(uint256 node, uint256 position)\\n internal\\n pure\\n returns (uint256)\\n {\\n unchecked {\\n uint256 shiftBits = position * Constants.SLOT_WIDTH;\\n // Shifting `SLOT_MAX` left by `slotShift(position)` bits\\n // gives us a number where all bits of the `position`th slot are set,\\n // and all other bits are unset.\\n //\\n // Using a bitwise NOT on this number,\\n // we get a uint256 where all bits are set\\n // except for those of the `position`th slot.\\n //\\n // Bitwise ANDing the original `node` with this number\\n // sets the bits of `position`th slot to zero,\\n // leaving all other bits unchanged.\\n return node & ~(Constants.SLOT_MAX << shiftBits);\\n }\\n }\\n\\n /// @notice Return `node` with the `position`th slot set to `weight`.\\n ///\\n /// @param weight The weight of of the node.\\n /// Safely truncated to a 32-bit number,\\n /// but this should never be called with an overflowing weight regardless.\\n function setSlot(\\n uint256 node,\\n uint256 position,\\n uint256 weight\\n ) internal pure returns (uint256) {\\n unchecked {\\n uint256 shiftBits = position * Constants.SLOT_WIDTH;\\n // Clear the `position`th slot like in `clearSlot()`.\\n uint256 clearedNode = node & ~(Constants.SLOT_MAX << shiftBits);\\n // Bitwise AND `weight` with `SLOT_MAX`\\n // to clear all but the 32 least significant bits.\\n //\\n // Shift this left by `slotShift(position)` bits\\n // to obtain a uint256 with all bits unset\\n // except in the `position`th slot\\n // which contains the 32-bit value of `weight`.\\n uint256 shiftedWeight = (weight & Constants.SLOT_MAX) << shiftBits;\\n // When we bitwise OR these together,\\n // all other slots except the `position`th one come from the left argument,\\n // and the `position`th gets filled with `weight` from the right argument.\\n return clearedNode | shiftedWeight;\\n }\\n }\\n\\n /// @notice Calculate the summed weight of all slots in the `node`.\\n function sumWeight(uint256 node) internal pure returns (uint256 sum) {\\n unchecked {\\n sum = node & Constants.SLOT_MAX;\\n // Iterate through each slot\\n // by shifting `node` right in increments of 32 bits,\\n // and adding the 32 least significant bits to the `sum`.\\n uint256 newNode = node >> Constants.SLOT_WIDTH;\\n while (newNode > 0) {\\n sum += (newNode & Constants.SLOT_MAX);\\n newNode = newNode >> Constants.SLOT_WIDTH;\\n }\\n return sum;\\n }\\n }\\n\\n /// @notice Pick a slot in `node` that corresponds to `index`.\\n /// Treats the node like an array of virtual stakers,\\n /// the number of virtual stakers in each slot corresponding to its weight,\\n /// and picks which slot contains the `index`th virtual staker.\\n ///\\n /// @dev Requires that `index` be lower than `sumWeight(node)`.\\n /// However, this is not enforced for performance reasons.\\n /// If `index` exceeds the permitted range,\\n /// `pickWeightedSlot()` returns the rightmost slot\\n /// and an excessively high `newIndex`.\\n ///\\n /// @return slot The slot of `node` containing the `index`th virtual staker.\\n ///\\n /// @return newIndex The index of the `index`th virtual staker of `node`\\n /// within the returned slot.\\n function pickWeightedSlot(uint256 node, uint256 index)\\n internal\\n pure\\n returns (uint256 slot, uint256 newIndex)\\n {\\n unchecked {\\n newIndex = index;\\n uint256 newNode = node;\\n uint256 currentSlotWeight = newNode & Constants.SLOT_MAX;\\n while (newIndex >= currentSlotWeight) {\\n newIndex -= currentSlotWeight;\\n slot++;\\n newNode = newNode >> Constants.SLOT_WIDTH;\\n currentSlotWeight = newNode & Constants.SLOT_MAX;\\n }\\n return (slot, newIndex);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xa7fb1c9c9789f30493e9a40e24a24f46875dc5e7630b4f67478167759f6d1882\"},\"@keep-network/sortition-pools/contracts/Chaosnet.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\n/// @title Chaosnet\\n/// @notice This is a beta staker program for stakers willing to go the extra\\n/// mile with monitoring, share their logs with the dev team, and allow to more\\n/// carefully monitor the bootstrapping network. As the network matures, the\\n/// beta program will be ended.\\ncontract Chaosnet {\\n /// @notice Indicates if the chaosnet is active. The chaosnet is active\\n /// after the contract deployment and can be ended with a call to\\n /// `deactivateChaosnet()`. Once deactivated chaosnet can not be activated\\n /// again.\\n bool public isChaosnetActive;\\n\\n /// @notice Indicates if the given operator is a beta operator for chaosnet.\\n mapping(address => bool) public isBetaOperator;\\n\\n /// @notice Address controlling chaosnet status and beta operator addresses.\\n address public chaosnetOwner;\\n\\n event BetaOperatorsAdded(address[] operators);\\n\\n event ChaosnetOwnerRoleTransferred(\\n address oldChaosnetOwner,\\n address newChaosnetOwner\\n );\\n\\n event ChaosnetDeactivated();\\n\\n constructor() {\\n _transferChaosnetOwner(msg.sender);\\n isChaosnetActive = true;\\n }\\n\\n modifier onlyChaosnetOwner() {\\n require(msg.sender == chaosnetOwner, \\\"Not the chaosnet owner\\\");\\n _;\\n }\\n\\n modifier onlyOnChaosnet() {\\n require(isChaosnetActive, \\\"Chaosnet is not active\\\");\\n _;\\n }\\n\\n /// @notice Adds beta operator to chaosnet. Can be called only by the\\n /// chaosnet owner when the chaosnet is active. Once the operator is added\\n /// as a beta operator, it can not be removed.\\n function addBetaOperators(address[] calldata operators)\\n public\\n onlyOnChaosnet\\n onlyChaosnetOwner\\n {\\n for (uint256 i = 0; i < operators.length; i++) {\\n isBetaOperator[operators[i]] = true;\\n }\\n\\n emit BetaOperatorsAdded(operators);\\n }\\n\\n /// @notice Deactivates the chaosnet. Can be called only by the chaosnet\\n /// owner. Once deactivated chaosnet can not be activated again.\\n function deactivateChaosnet() public onlyOnChaosnet onlyChaosnetOwner {\\n isChaosnetActive = false;\\n emit ChaosnetDeactivated();\\n }\\n\\n /// @notice Transfers the chaosnet owner role to another non-zero address.\\n function transferChaosnetOwnerRole(address newChaosnetOwner)\\n public\\n onlyChaosnetOwner\\n {\\n require(\\n newChaosnetOwner != address(0),\\n \\\"New chaosnet owner must not be zero address\\\"\\n );\\n _transferChaosnetOwner(newChaosnetOwner);\\n }\\n\\n function _transferChaosnetOwner(address newChaosnetOwner) internal {\\n address oldChaosnetOwner = chaosnetOwner;\\n chaosnetOwner = newChaosnetOwner;\\n emit ChaosnetOwnerRoleTransferred(oldChaosnetOwner, newChaosnetOwner);\\n }\\n}\\n\",\"keccak256\":\"0xeaf7bdd5626f88c329793a012621039692ce1b6e1f13013997ddb13d7e3032df\"},\"@keep-network/sortition-pools/contracts/Constants.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nlibrary Constants {\\n ////////////////////////////////////////////////////////////////////////////\\n // Parameters for configuration\\n\\n // How many bits a position uses per level of the tree;\\n // each branch of the tree contains 2**SLOT_BITS slots.\\n uint256 constant SLOT_BITS = 3;\\n uint256 constant LEVELS = 7;\\n ////////////////////////////////////////////////////////////////////////////\\n\\n ////////////////////////////////////////////////////////////////////////////\\n // Derived constants, do not touch\\n uint256 constant SLOT_COUNT = 2**SLOT_BITS;\\n uint256 constant SLOT_WIDTH = 256 / SLOT_COUNT;\\n uint256 constant LAST_SLOT = SLOT_COUNT - 1;\\n uint256 constant SLOT_MAX = (2**SLOT_WIDTH) - 1;\\n uint256 constant POOL_CAPACITY = SLOT_COUNT**LEVELS;\\n\\n uint256 constant ID_WIDTH = SLOT_WIDTH;\\n uint256 constant ID_MAX = SLOT_MAX;\\n\\n uint256 constant BLOCKHEIGHT_WIDTH = 96 - ID_WIDTH;\\n uint256 constant BLOCKHEIGHT_MAX = (2**BLOCKHEIGHT_WIDTH) - 1;\\n\\n uint256 constant SLOT_POINTER_MAX = (2**SLOT_BITS) - 1;\\n uint256 constant LEAF_FLAG = 1 << 255;\\n\\n uint256 constant WEIGHT_WIDTH = 256 / SLOT_COUNT;\\n ////////////////////////////////////////////////////////////////////////////\\n}\\n\",\"keccak256\":\"0xaef690ced707935745ff1482b7bb9bd9eb77bf6a39c717465e64cf12db8a7d39\"},\"@keep-network/sortition-pools/contracts/Leaf.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Constants.sol\\\";\\n\\nlibrary Leaf {\\n function make(\\n address _operator,\\n uint256 _creationBlock,\\n uint256 _id\\n ) internal pure returns (uint256) {\\n assert(_creationBlock <= type(uint64).max);\\n assert(_id <= type(uint32).max);\\n // Converting a bytesX type into a larger type\\n // adds zero bytes on the right.\\n uint256 op = uint256(bytes32(bytes20(_operator)));\\n // Bitwise AND the id to erase\\n // all but the 32 least significant bits\\n uint256 uid = _id & Constants.ID_MAX;\\n // Erase all but the 64 least significant bits,\\n // then shift left by 32 bits to make room for the id\\n uint256 cb = (_creationBlock & Constants.BLOCKHEIGHT_MAX) <<\\n Constants.ID_WIDTH;\\n // Bitwise OR them all together to get\\n // [address operator || uint64 creationBlock || uint32 id]\\n return (op | cb | uid);\\n }\\n\\n function operator(uint256 leaf) internal pure returns (address) {\\n // Converting a bytesX type into a smaller type\\n // truncates it on the right.\\n return address(bytes20(bytes32(leaf)));\\n }\\n\\n /// @notice Return the block number the leaf was created in.\\n function creationBlock(uint256 leaf) internal pure returns (uint256) {\\n return ((leaf >> Constants.ID_WIDTH) & Constants.BLOCKHEIGHT_MAX);\\n }\\n\\n function id(uint256 leaf) internal pure returns (uint32) {\\n // Id is stored in the 32 least significant bits.\\n // Bitwise AND ensures that we only get the contents of those bits.\\n return uint32(leaf & Constants.ID_MAX);\\n }\\n}\\n\",\"keccak256\":\"0xbd107a1a43e48884885e5e966ffcbcd8fa5e89863715d717bb4006e9f89cdc2b\"},\"@keep-network/sortition-pools/contracts/Position.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Constants.sol\\\";\\n\\nlibrary Position {\\n // Return the last 3 bits of a position number,\\n // corresponding to its slot in its parent\\n function slot(uint256 a) internal pure returns (uint256) {\\n return a & Constants.SLOT_POINTER_MAX;\\n }\\n\\n // Return the parent of a position number\\n function parent(uint256 a) internal pure returns (uint256) {\\n return a >> Constants.SLOT_BITS;\\n }\\n\\n // Return the location of the child of a at the given slot\\n function child(uint256 a, uint256 s) internal pure returns (uint256) {\\n return (a << Constants.SLOT_BITS) | (s & Constants.SLOT_POINTER_MAX); // slot(s)\\n }\\n\\n // Return the uint p as a flagged position uint:\\n // the least significant 21 bits contain the position\\n // and the 22nd bit is set as a flag\\n // to distinguish the position 0x000000 from an empty field.\\n function setFlag(uint256 p) internal pure returns (uint256) {\\n return p | Constants.LEAF_FLAG;\\n }\\n\\n // Turn a flagged position into an unflagged position\\n // by removing the flag at the 22nd least significant bit.\\n //\\n // We shouldn't _actually_ need this\\n // as all position-manipulating code should ignore non-position bits anyway\\n // but it's cheap to call so might as well do it.\\n function unsetFlag(uint256 p) internal pure returns (uint256) {\\n return p & (~Constants.LEAF_FLAG);\\n }\\n}\\n\",\"keccak256\":\"0xd3a927908080ac21353a92a6bce3d69e94a5c30f6b51f16b271b6cc679f110e2\"},\"@keep-network/sortition-pools/contracts/RNG.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Leaf.sol\\\";\\nimport \\\"./Constants.sol\\\";\\n\\nlibrary RNG {\\n /// @notice Get an index in the range `[0 .. range-1]`\\n /// and the new state of the RNG,\\n /// using the provided `state` of the RNG.\\n ///\\n /// @param range The upper bound of the index, exclusive.\\n ///\\n /// @param state The previous state of the RNG.\\n /// The initial state needs to be obtained\\n /// from a trusted randomness oracle (the random beacon),\\n /// or from a chain of earlier calls to `RNG.getIndex()`\\n /// on an originally trusted seed.\\n ///\\n /// @dev Calculates the number of bits required for the desired range,\\n /// takes the least significant bits of `state`\\n /// and checks if the obtained index is within the desired range.\\n /// The original state is hashed with `keccak256` to get a new state.\\n /// If the index is outside the range,\\n /// the function retries until it gets a suitable index.\\n ///\\n /// @return index A random integer between `0` and `range - 1`, inclusive.\\n ///\\n /// @return newState The new state of the RNG.\\n /// When `getIndex()` is called one or more times,\\n /// care must be taken to always use the output `state`\\n /// of the most recent call as the input `state` of a subsequent call.\\n /// At the end of a transaction calling `RNG.getIndex()`,\\n /// the previous stored state must be overwritten with the latest output.\\n function getIndex(\\n uint256 range,\\n bytes32 state,\\n uint256 bits\\n ) internal view returns (uint256, bytes32) {\\n bool found = false;\\n uint256 index = 0;\\n bytes32 newState = state;\\n while (!found) {\\n index = truncate(bits, uint256(newState));\\n newState = keccak256(abi.encodePacked(newState, address(this)));\\n if (index < range) {\\n found = true;\\n }\\n }\\n return (index, newState);\\n }\\n\\n /// @notice Calculate how many bits are required\\n /// for an index in the range `[0 .. range-1]`.\\n ///\\n /// @param range The upper bound of the desired range, exclusive.\\n ///\\n /// @return uint The smallest number of bits\\n /// that can contain the number `range-1`.\\n function bitsRequired(uint256 range) internal pure returns (uint256) {\\n unchecked {\\n if (range == 1) {\\n return 0;\\n }\\n\\n uint256 bits = Constants.WEIGHT_WIDTH - 1;\\n\\n // Left shift by `bits`,\\n // so we have a 1 in the (bits + 1)th least significant bit\\n // and 0 in other bits.\\n // If this number is equal or greater than `range`,\\n // the range [0, range-1] fits in `bits` bits.\\n //\\n // Because we loop from high bits to low bits,\\n // we find the highest number of bits that doesn't fit the range,\\n // and return that number + 1.\\n while (1 << bits >= range) {\\n bits--;\\n }\\n\\n return bits + 1;\\n }\\n }\\n\\n /// @notice Truncate `input` to the `bits` least significant bits.\\n function truncate(uint256 bits, uint256 input)\\n internal\\n pure\\n returns (uint256)\\n {\\n unchecked {\\n return input & ((1 << bits) - 1);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x67f87f589cd5123ffa32f883ea2f09b0e56258508bae82b8c655b3c27c71eb5e\"},\"@keep-network/sortition-pools/contracts/Rewards.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\n/// @title Rewards\\n/// @notice Rewards are allocated proportionally to operators\\n/// present in the pool at payout based on their weight in the pool.\\n///\\n/// To facilitate this, we use a global accumulator value\\n/// to track the total rewards one unit of weight would've earned\\n/// since the creation of the pool.\\n///\\n/// Whenever a reward is paid, the accumulator is increased\\n/// by the size of the reward divided by the total weight\\n/// of all eligible operators in the pool.\\n///\\n/// Each operator has an individual accumulator value,\\n/// set to equal the global accumulator when the operator joins the pool.\\n/// This accumulator reflects the amount of rewards\\n/// that have already been accounted for with that operator.\\n///\\n/// Whenever an operator's weight in the pool changes,\\n/// we can update the amount of rewards the operator has earned\\n/// by subtracting the operator's accumulator from the global accumulator.\\n/// This gives us the amount of rewards one unit of weight has earned\\n/// since the last time the operator's rewards have been updated.\\n/// Then we multiply that by the operator's previous (pre-change) weight\\n/// to determine how much rewards in total the operator has earned,\\n/// and add this to the operator's earned rewards.\\n/// Finally, we set the operator's accumulator to the global accumulator value.\\ncontract Rewards {\\n struct OperatorRewards {\\n // The state of the global accumulator\\n // when the operator's rewards were last updated\\n uint96 accumulated;\\n // The amount of rewards collected by the operator after the latest update.\\n // The amount the operator could withdraw may equal `available`\\n // or it may be greater, if more rewards have been paid in since then.\\n // To evaulate the most recent amount including rewards potentially paid\\n // since the last update, use `availableRewards` function.\\n uint96 available;\\n // If nonzero, the operator is ineligible for rewards\\n // and may only re-enable rewards after the specified timestamp.\\n // XXX: unsigned 32-bit integer unix seconds, will break around 2106\\n uint32 ineligibleUntil;\\n // Locally cached weight of the operator,\\n // used to reduce the cost of setting operators ineligible.\\n uint32 weight;\\n }\\n\\n // The global accumulator of how much rewards\\n // a hypothetical operator of weight 1 would have earned\\n // since the creation of the pool.\\n uint96 internal globalRewardAccumulator;\\n // If the amount of reward tokens paid in\\n // does not divide cleanly by pool weight,\\n // the difference is recorded as rounding dust\\n // and added to the next reward.\\n uint96 internal rewardRoundingDust;\\n\\n // The amount of rewards that would've been earned by ineligible operators\\n // had they not been ineligible.\\n uint96 public ineligibleEarnedRewards;\\n\\n // Ineligibility times are calculated from this offset,\\n // set at contract creation.\\n uint256 internal immutable ineligibleOffsetStart;\\n\\n mapping(uint32 => OperatorRewards) internal operatorRewards;\\n\\n constructor() {\\n // solhint-disable-next-line not-rely-on-time\\n ineligibleOffsetStart = block.timestamp;\\n }\\n\\n /// @notice Return whether the operator is eligible for rewards or not.\\n function isEligibleForRewards(uint32 operator) internal view returns (bool) {\\n return operatorRewards[operator].ineligibleUntil == 0;\\n }\\n\\n /// @notice Return the time the operator's reward eligibility can be restored.\\n function rewardsEligibilityRestorableAt(uint32 operator)\\n internal\\n view\\n returns (uint256)\\n {\\n uint32 until = operatorRewards[operator].ineligibleUntil;\\n require(until != 0, \\\"Operator already eligible\\\");\\n return (uint256(until) + ineligibleOffsetStart);\\n }\\n\\n /// @notice Return whether the operator is able to restore their eligibility\\n /// for rewards right away.\\n function canRestoreRewardEligibility(uint32 operator)\\n internal\\n view\\n returns (bool)\\n {\\n // solhint-disable-next-line not-rely-on-time\\n return rewardsEligibilityRestorableAt(operator) <= block.timestamp;\\n }\\n\\n /// @notice Internal function for updating the global state of rewards.\\n function addRewards(uint96 rewardAmount, uint32 currentPoolWeight) internal {\\n require(currentPoolWeight > 0, \\\"No recipients in pool\\\");\\n\\n uint96 totalAmount = rewardAmount + rewardRoundingDust;\\n uint96 perWeightReward = totalAmount / currentPoolWeight;\\n uint96 newRoundingDust = totalAmount % currentPoolWeight;\\n\\n globalRewardAccumulator += perWeightReward;\\n rewardRoundingDust = newRoundingDust;\\n }\\n\\n /// @notice Internal function for updating the operator's reward state.\\n function updateOperatorRewards(uint32 operator, uint32 newWeight) internal {\\n uint96 acc = globalRewardAccumulator;\\n OperatorRewards memory o = operatorRewards[operator];\\n uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);\\n if (o.ineligibleUntil == 0) {\\n // If operator is not ineligible, update their earned rewards\\n o.available += accruedRewards;\\n } else {\\n // If ineligible, put the rewards into the ineligible pot\\n ineligibleEarnedRewards += accruedRewards;\\n }\\n // In any case, update their accumulator and weight\\n o.accumulated = acc;\\n o.weight = newWeight;\\n operatorRewards[operator] = o;\\n }\\n\\n /// @notice Set the amount of withdrawable tokens to zero\\n /// and return the previous withdrawable amount.\\n /// @dev Does not update the withdrawable amount,\\n /// but should usually be accompanied by an update.\\n function withdrawOperatorRewards(uint32 operator)\\n internal\\n returns (uint96 withdrawable)\\n {\\n OperatorRewards storage o = operatorRewards[operator];\\n withdrawable = o.available;\\n o.available = 0;\\n }\\n\\n /// @notice Set the amount of ineligible-earned tokens to zero\\n /// and return the previous amount.\\n function withdrawIneligibleRewards() internal returns (uint96 withdrawable) {\\n withdrawable = ineligibleEarnedRewards;\\n ineligibleEarnedRewards = 0;\\n }\\n\\n /// @notice Set the given operators as ineligible for rewards.\\n /// The operators can restore their eligibility at the given time.\\n function setIneligible(uint32[] memory operators, uint256 until) internal {\\n OperatorRewards memory o = OperatorRewards(0, 0, 0, 0);\\n uint96 globalAcc = globalRewardAccumulator;\\n uint96 accrued = 0;\\n // Record ineligibility as seconds after contract creation\\n uint32 _until = uint32(until - ineligibleOffsetStart);\\n\\n for (uint256 i = 0; i < operators.length; i++) {\\n uint32 operator = operators[i];\\n OperatorRewards storage r = operatorRewards[operator];\\n o.available = r.available;\\n o.accumulated = r.accumulated;\\n o.ineligibleUntil = r.ineligibleUntil;\\n o.weight = r.weight;\\n\\n if (o.ineligibleUntil != 0) {\\n // If operator is already ineligible,\\n // don't earn rewards or shorten its ineligibility\\n if (o.ineligibleUntil < _until) {\\n o.ineligibleUntil = _until;\\n }\\n } else {\\n // The operator becomes ineligible -> earn rewards\\n o.ineligibleUntil = _until;\\n accrued = (globalAcc - o.accumulated) * uint96(o.weight);\\n o.available += accrued;\\n }\\n o.accumulated = globalAcc;\\n\\n r.available = o.available;\\n r.accumulated = o.accumulated;\\n r.ineligibleUntil = o.ineligibleUntil;\\n r.weight = o.weight;\\n }\\n }\\n\\n /// @notice Restore the given operator's eligibility for rewards.\\n function restoreEligibility(uint32 operator) internal {\\n // solhint-disable-next-line not-rely-on-time\\n require(canRestoreRewardEligibility(operator), \\\"Operator still ineligible\\\");\\n uint96 acc = globalRewardAccumulator;\\n OperatorRewards memory o = operatorRewards[operator];\\n uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);\\n ineligibleEarnedRewards += accruedRewards;\\n o.accumulated = acc;\\n o.ineligibleUntil = 0;\\n operatorRewards[operator] = o;\\n }\\n\\n /// @notice Returns the amount of rewards currently available for withdrawal\\n /// for the given operator.\\n function availableRewards(uint32 operator) internal view returns (uint96) {\\n uint96 acc = globalRewardAccumulator;\\n OperatorRewards memory o = operatorRewards[operator];\\n if (o.ineligibleUntil == 0) {\\n // If operator is not ineligible, calculate newly accrued rewards and add\\n // them to the available ones, calculated during the last update.\\n uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);\\n return o.available + accruedRewards;\\n } else {\\n // If ineligible, return only the rewards calculated during the last\\n // update.\\n return o.available;\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3a10abb408b44335a092387b2c7ee01db3b27997f1f2c888d9b7a2d92934c4e2\"},\"@keep-network/sortition-pools/contracts/SortitionPool.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\\\";\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./RNG.sol\\\";\\nimport \\\"./SortitionTree.sol\\\";\\nimport \\\"./Rewards.sol\\\";\\nimport \\\"./Chaosnet.sol\\\";\\n\\n/// @title Sortition Pool\\n/// @notice A logarithmic data structure used to store the pool of eligible\\n/// operators weighted by their stakes. It allows to select a group of operators\\n/// based on the provided pseudo-random seed.\\ncontract SortitionPool is\\n SortitionTree,\\n Rewards,\\n Ownable,\\n Chaosnet,\\n IReceiveApproval\\n{\\n using Branch for uint256;\\n using Leaf for uint256;\\n using Position for uint256;\\n\\n IERC20WithPermit public immutable rewardToken;\\n\\n uint256 public immutable poolWeightDivisor;\\n\\n bool public isLocked;\\n\\n event IneligibleForRewards(uint32[] ids, uint256 until);\\n\\n event RewardEligibilityRestored(address indexed operator, uint32 indexed id);\\n\\n /// @notice Reverts if called while pool is locked.\\n modifier onlyUnlocked() {\\n require(!isLocked, \\\"Sortition pool locked\\\");\\n _;\\n }\\n\\n /// @notice Reverts if called while pool is unlocked.\\n modifier onlyLocked() {\\n require(isLocked, \\\"Sortition pool unlocked\\\");\\n _;\\n }\\n\\n constructor(IERC20WithPermit _rewardToken, uint256 _poolWeightDivisor) {\\n rewardToken = _rewardToken;\\n poolWeightDivisor = _poolWeightDivisor;\\n }\\n\\n function receiveApproval(\\n address sender,\\n uint256 amount,\\n address token,\\n bytes calldata\\n ) external override {\\n require(token == address(rewardToken), \\\"Unsupported token\\\");\\n rewardToken.transferFrom(sender, address(this), amount);\\n Rewards.addRewards(uint96(amount), uint32(root.sumWeight()));\\n }\\n\\n /// @notice Withdraws all available rewards for the given operator to the\\n /// given beneficiary.\\n /// @dev Can be called only be the owner. Does not validate if the provided\\n /// beneficiary is associated with the provided operator - this needs to\\n /// be done by the owner calling this function.\\n /// @return The amount of rewards withdrawn in this call.\\n function withdrawRewards(address operator, address beneficiary)\\n public\\n onlyOwner\\n returns (uint96)\\n {\\n uint32 id = getOperatorID(operator);\\n Rewards.updateOperatorRewards(id, uint32(getPoolWeight(operator)));\\n uint96 earned = Rewards.withdrawOperatorRewards(id);\\n rewardToken.transfer(beneficiary, uint256(earned));\\n return earned;\\n }\\n\\n /// @notice Withdraws rewards not allocated to operators marked as ineligible\\n /// to the given recipient address.\\n /// @dev Can be called only by the owner.\\n function withdrawIneligible(address recipient) public onlyOwner {\\n uint96 earned = Rewards.withdrawIneligibleRewards();\\n rewardToken.transfer(recipient, uint256(earned));\\n }\\n\\n /// @notice Locks the sortition pool. In locked state, members cannot be\\n /// inserted and removed from the pool. Members statuses cannot\\n /// be updated as well.\\n /// @dev Can be called only by the contract owner.\\n function lock() public onlyOwner {\\n isLocked = true;\\n }\\n\\n /// @notice Unlocks the sortition pool. Removes all restrictions set by\\n /// the `lock` method.\\n /// @dev Can be called only by the contract owner.\\n function unlock() public onlyOwner {\\n isLocked = false;\\n }\\n\\n /// @notice Inserts an operator to the pool. Reverts if the operator is\\n /// already present. Reverts if the operator is not eligible because of their\\n /// authorized stake. Reverts if the chaosnet is active and the operator is\\n /// not a beta operator.\\n /// @dev Can be called only by the contract owner.\\n /// @param operator Address of the inserted operator.\\n /// @param authorizedStake Inserted operator's authorized stake for the application.\\n function insertOperator(address operator, uint256 authorizedStake)\\n public\\n onlyOwner\\n // TODO: Disabled temporarily for a test environment. This should never be disabled\\n // in normal circumstances.\\n // onlyUnlocked\\n {\\n uint256 weight = getWeight(authorizedStake);\\n require(weight > 0, \\\"Operator not eligible\\\");\\n\\n if (isChaosnetActive) {\\n require(isBetaOperator[operator], \\\"Not beta operator for chaosnet\\\");\\n }\\n\\n _insertOperator(operator, weight);\\n uint32 id = getOperatorID(operator);\\n Rewards.updateOperatorRewards(id, uint32(weight));\\n }\\n\\n /// @notice Update the operator's weight if present and eligible,\\n /// or remove from the pool if present and ineligible.\\n /// @dev Can be called only by the contract owner.\\n /// @param operator Address of the updated operator.\\n /// @param authorizedStake Operator's authorized stake for the application.\\n function updateOperatorStatus(address operator, uint256 authorizedStake)\\n public\\n onlyOwner\\n onlyUnlocked\\n {\\n uint256 weight = getWeight(authorizedStake);\\n\\n uint32 id = getOperatorID(operator);\\n Rewards.updateOperatorRewards(id, uint32(weight));\\n\\n if (weight == 0) {\\n _removeOperator(operator);\\n } else {\\n updateOperator(operator, weight);\\n }\\n }\\n\\n /// @notice Set the given operators as ineligible for rewards.\\n /// The operators can restore their eligibility at the given time.\\n function setRewardIneligibility(uint32[] calldata operators, uint256 until)\\n public\\n onlyOwner\\n {\\n Rewards.setIneligible(operators, until);\\n emit IneligibleForRewards(operators, until);\\n }\\n\\n /// @notice Restores reward eligibility for the operator.\\n function restoreRewardEligibility(address operator) public {\\n uint32 id = getOperatorID(operator);\\n Rewards.restoreEligibility(id);\\n emit RewardEligibilityRestored(operator, id);\\n }\\n\\n /// @notice Returns whether the operator is eligible for rewards or not.\\n function isEligibleForRewards(address operator) public view returns (bool) {\\n uint32 id = getOperatorID(operator);\\n return Rewards.isEligibleForRewards(id);\\n }\\n\\n /// @notice Returns the time the operator's reward eligibility can be restored.\\n function rewardsEligibilityRestorableAt(address operator)\\n public\\n view\\n returns (uint256)\\n {\\n uint32 id = getOperatorID(operator);\\n return Rewards.rewardsEligibilityRestorableAt(id);\\n }\\n\\n /// @notice Returns whether the operator is able to restore their eligibility\\n /// for rewards right away.\\n function canRestoreRewardEligibility(address operator)\\n public\\n view\\n returns (bool)\\n {\\n uint32 id = getOperatorID(operator);\\n return Rewards.canRestoreRewardEligibility(id);\\n }\\n\\n /// @notice Returns the amount of rewards withdrawable for the given operator.\\n function getAvailableRewards(address operator) public view returns (uint96) {\\n uint32 id = getOperatorID(operator);\\n return availableRewards(id);\\n }\\n\\n /// @notice Return whether the operator is present in the pool.\\n function isOperatorInPool(address operator) public view returns (bool) {\\n return getFlaggedLeafPosition(operator) != 0;\\n }\\n\\n /// @notice Return whether the operator's weight in the pool\\n /// matches their eligible weight.\\n function isOperatorUpToDate(address operator, uint256 authorizedStake)\\n public\\n view\\n returns (bool)\\n {\\n return getWeight(authorizedStake) == getPoolWeight(operator);\\n }\\n\\n /// @notice Return the weight of the operator in the pool,\\n /// which may or may not be out of date.\\n function getPoolWeight(address operator) public view returns (uint256) {\\n uint256 flaggedPosition = getFlaggedLeafPosition(operator);\\n if (flaggedPosition == 0) {\\n return 0;\\n } else {\\n uint256 leafPosition = flaggedPosition.unsetFlag();\\n uint256 leafWeight = getLeafWeight(leafPosition);\\n return leafWeight;\\n }\\n }\\n\\n /// @notice Selects a new group of operators of the provided size based on\\n /// the provided pseudo-random seed. At least one operator has to be\\n /// registered in the pool, otherwise the function fails reverting the\\n /// transaction.\\n /// @param groupSize Size of the requested group\\n /// @param seed Pseudo-random number used to select operators to group\\n /// @return selected Members of the selected group\\n function selectGroup(uint256 groupSize, bytes32 seed)\\n public\\n view\\n onlyLocked\\n returns (uint32[] memory)\\n {\\n uint256 _root = root;\\n\\n bytes32 rngState = seed;\\n uint256 rngRange = _root.sumWeight();\\n require(rngRange > 0, \\\"Not enough operators in pool\\\");\\n uint256 currentIndex;\\n\\n uint256 bits = RNG.bitsRequired(rngRange);\\n\\n uint32[] memory selected = new uint32[](groupSize);\\n\\n for (uint256 i = 0; i < groupSize; i++) {\\n (currentIndex, rngState) = RNG.getIndex(rngRange, rngState, bits);\\n\\n uint256 leafPosition = pickWeightedLeaf(currentIndex, _root);\\n\\n uint256 leaf = leaves[leafPosition];\\n selected[i] = leaf.id();\\n }\\n return selected;\\n }\\n\\n function getWeight(uint256 authorization) internal view returns (uint256) {\\n return authorization / poolWeightDivisor;\\n }\\n}\\n\",\"keccak256\":\"0x3c330b682bcb722bba861077a229f0605cda2142c3f0d45aaf2ce6a295e80615\"},\"@keep-network/sortition-pools/contracts/SortitionTree.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Branch.sol\\\";\\nimport \\\"./Position.sol\\\";\\nimport \\\"./Leaf.sol\\\";\\nimport \\\"./Constants.sol\\\";\\n\\ncontract SortitionTree {\\n using Branch for uint256;\\n using Position for uint256;\\n using Leaf for uint256;\\n\\n // implicit tree\\n // root 8\\n // level2 64\\n // level3 512\\n // level4 4k\\n // level5 32k\\n // level6 256k\\n // level7 2M\\n uint256 internal root;\\n\\n // A 2-index mapping from layer => (index (0-index) => branch). For example,\\n // to access the 6th branch in the 2nd layer (right below the root node; the\\n // first branch layer), call branches[2][5]. Mappings are used in place of\\n // arrays for efficiency. The root is the first layer, the branches occupy\\n // layers 2 through 7, and layer 8 is for the leaves. Following this\\n // convention, the first index in `branches` is `2`, and the last index is\\n // `7`.\\n mapping(uint256 => mapping(uint256 => uint256)) internal branches;\\n\\n // A 0-index mapping from index => leaf, acting as an array. For example, to\\n // access the 42nd leaf, call leaves[41].\\n mapping(uint256 => uint256) internal leaves;\\n\\n // the flagged (see setFlag() and unsetFlag() in Position.sol) positions\\n // of all operators present in the pool\\n mapping(address => uint256) internal flaggedLeafPosition;\\n\\n // the leaf after the rightmost occupied leaf of each stack\\n uint256 internal rightmostLeaf;\\n\\n // the empty leaves in each stack\\n // between 0 and the rightmost occupied leaf\\n uint256[] internal emptyLeaves;\\n\\n // Each operator has an uint32 ID number\\n // which is allocated when they first join the pool\\n // and remains unchanged even if they leave and rejoin the pool.\\n mapping(address => uint32) internal operatorID;\\n\\n // The idAddress array records the address corresponding to each ID number.\\n // The ID number 0 is initialized with a zero address and is not used.\\n address[] internal idAddress;\\n\\n constructor() {\\n root = 0;\\n rightmostLeaf = 0;\\n idAddress.push();\\n }\\n\\n /// @notice Return the ID number of the given operator address. An ID number\\n /// of 0 means the operator has not been allocated an ID number yet.\\n /// @param operator Address of the operator.\\n /// @return the ID number of the given operator address\\n function getOperatorID(address operator) public view returns (uint32) {\\n return operatorID[operator];\\n }\\n\\n /// @notice Get the operator address corresponding to the given ID number. A\\n /// zero address means the ID number has not been allocated yet.\\n /// @param id ID of the operator\\n /// @return the address of the operator\\n function getIDOperator(uint32 id) public view returns (address) {\\n return idAddress.length > id ? idAddress[id] : address(0);\\n }\\n\\n /// @notice Gets the operator addresses corresponding to the given ID\\n /// numbers. A zero address means the ID number has not been allocated yet.\\n /// This function works just like getIDOperator except that it allows to fetch\\n /// operator addresses for multiple IDs in one call.\\n /// @param ids the array of the operator ids\\n /// @return an array of the associated operator addresses\\n function getIDOperators(uint32[] calldata ids)\\n public\\n view\\n returns (address[] memory)\\n {\\n uint256 idCount = idAddress.length;\\n\\n address[] memory operators = new address[](ids.length);\\n for (uint256 i = 0; i < ids.length; i++) {\\n uint32 id = ids[i];\\n operators[i] = idCount > id ? idAddress[id] : address(0);\\n }\\n return operators;\\n }\\n\\n /// @notice Checks if operator is already registered in the pool.\\n /// @param operator the address of the operator\\n /// @return whether or not the operator is already registered in the pool\\n function isOperatorRegistered(address operator) public view returns (bool) {\\n return getFlaggedLeafPosition(operator) != 0;\\n }\\n\\n /// @notice Sum the number of operators in each trunk.\\n /// @return the number of operators in the pool\\n function operatorsInPool() public view returns (uint256) {\\n // Get the number of leaves that might be occupied;\\n // if `rightmostLeaf` equals `firstLeaf()` the tree must be empty,\\n // otherwise the difference between these numbers\\n // gives the number of leaves that may be occupied.\\n uint256 nPossiblyUsedLeaves = rightmostLeaf;\\n // Get the number of empty leaves\\n // not accounted for by the `rightmostLeaf`\\n uint256 nEmptyLeaves = emptyLeaves.length;\\n\\n return (nPossiblyUsedLeaves - nEmptyLeaves);\\n }\\n\\n /// @notice Convenience method to return the total weight of the pool\\n /// @return the total weight of the pool\\n function totalWeight() public view returns (uint256) {\\n return root.sumWeight();\\n }\\n\\n /// @notice Give the operator a new ID number.\\n /// Does not check if the operator already has an ID number.\\n /// @param operator the address of the operator\\n /// @return a new ID for that operator\\n function allocateOperatorID(address operator) internal returns (uint256) {\\n uint256 id = idAddress.length;\\n\\n require(id <= type(uint32).max, \\\"Pool capacity exceeded\\\");\\n\\n operatorID[operator] = uint32(id);\\n idAddress.push(operator);\\n return id;\\n }\\n\\n /// @notice Inserts an operator into the sortition pool\\n /// @param operator the address of an operator to insert\\n /// @param weight how much weight that operator has in the pool\\n function _insertOperator(address operator, uint256 weight) internal {\\n require(\\n !isOperatorRegistered(operator),\\n \\\"Operator is already registered in the pool\\\"\\n );\\n\\n // Fetch the operator's ID, and if they don't have one, allocate them one.\\n uint256 id = getOperatorID(operator);\\n if (id == 0) {\\n id = allocateOperatorID(operator);\\n }\\n\\n // Determine which leaf to insert them into\\n uint256 position = getEmptyLeafPosition();\\n // Record the block the operator was inserted in\\n uint256 theLeaf = Leaf.make(operator, block.number, id);\\n\\n // Update the leaf, and propagate the weight changes all the way up to the\\n // root.\\n root = setLeaf(position, theLeaf, weight, root);\\n\\n // Without position flags,\\n // the position 0x000000 would be treated as empty\\n flaggedLeafPosition[operator] = position.setFlag();\\n }\\n\\n /// @notice Remove an operator (and their weight) from the pool.\\n /// @param operator the address of the operator to remove\\n function _removeOperator(address operator) internal {\\n uint256 flaggedPosition = getFlaggedLeafPosition(operator);\\n require(flaggedPosition != 0, \\\"Operator is not registered in the pool\\\");\\n uint256 unflaggedPosition = flaggedPosition.unsetFlag();\\n\\n // Update the leaf, and propagate the weight changes all the way up to the\\n // root.\\n root = removeLeaf(unflaggedPosition, root);\\n removeLeafPositionRecord(operator);\\n }\\n\\n /// @notice Update an operator's weight in the pool.\\n /// @param operator the address of the operator to update\\n /// @param weight the new weight\\n function updateOperator(address operator, uint256 weight) internal {\\n require(\\n isOperatorRegistered(operator),\\n \\\"Operator is not registered in the pool\\\"\\n );\\n\\n uint256 flaggedPosition = getFlaggedLeafPosition(operator);\\n uint256 unflaggedPosition = flaggedPosition.unsetFlag();\\n root = updateLeaf(unflaggedPosition, weight, root);\\n }\\n\\n /// @notice Helper method to remove a leaf position record for an operator.\\n /// @param operator the address of the operator to remove the record for\\n function removeLeafPositionRecord(address operator) internal {\\n flaggedLeafPosition[operator] = 0;\\n }\\n\\n /// @notice Removes the data and weight from a particular leaf.\\n /// @param position the leaf index to remove\\n /// @param _root the root node containing the leaf\\n /// @return the updated root node\\n function removeLeaf(uint256 position, uint256 _root)\\n internal\\n returns (uint256)\\n {\\n uint256 rightmostSubOne = rightmostLeaf - 1;\\n bool isRightmost = position == rightmostSubOne;\\n\\n // Clears out the data in the leaf node, and then propagates the weight\\n // changes all the way up to the root.\\n uint256 newRoot = setLeaf(position, 0, 0, _root);\\n\\n // Infer if need to fall back on emptyLeaves yet\\n if (isRightmost) {\\n rightmostLeaf = rightmostSubOne;\\n } else {\\n emptyLeaves.push(position);\\n }\\n return newRoot;\\n }\\n\\n /// @notice Updates the tree to give a particular leaf a new weight.\\n /// @param position the index of the leaf to update\\n /// @param weight the new weight\\n /// @param _root the root node containing the leaf\\n /// @return the updated root node\\n function updateLeaf(\\n uint256 position,\\n uint256 weight,\\n uint256 _root\\n ) internal returns (uint256) {\\n if (getLeafWeight(position) != weight) {\\n return updateTree(position, weight, _root);\\n } else {\\n return _root;\\n }\\n }\\n\\n /// @notice Places a leaf into a particular position, with a given weight and\\n /// propagates that change.\\n /// @param position the index to place the leaf in\\n /// @param theLeaf the new leaf to place in the position\\n /// @param leafWeight the weight of the leaf\\n /// @param _root the root containing the new leaf\\n /// @return the updated root node\\n function setLeaf(\\n uint256 position,\\n uint256 theLeaf,\\n uint256 leafWeight,\\n uint256 _root\\n ) internal returns (uint256) {\\n // set leaf\\n leaves[position] = theLeaf;\\n\\n return (updateTree(position, leafWeight, _root));\\n }\\n\\n /// @notice Propagates a weight change at a position through the tree,\\n /// eventually returning the updated root.\\n /// @param position the index of leaf to update\\n /// @param weight the new weight of the leaf\\n /// @param _root the root node containing the leaf\\n /// @return the updated root node\\n function updateTree(\\n uint256 position,\\n uint256 weight,\\n uint256 _root\\n ) internal returns (uint256) {\\n uint256 childSlot;\\n uint256 treeNode;\\n uint256 newNode;\\n uint256 nodeWeight = weight;\\n\\n uint256 parent = position;\\n // set levels 7 to 2\\n for (uint256 level = Constants.LEVELS; level >= 2; level--) {\\n childSlot = parent.slot();\\n parent = parent.parent();\\n treeNode = branches[level][parent];\\n newNode = treeNode.setSlot(childSlot, nodeWeight);\\n branches[level][parent] = newNode;\\n nodeWeight = newNode.sumWeight();\\n }\\n\\n // set level Root\\n childSlot = parent.slot();\\n return _root.setSlot(childSlot, nodeWeight);\\n }\\n\\n /// @notice Retrieves the next available empty leaf position. Tries to fill\\n /// left to right first, ignoring leaf removals, and then fills\\n /// most-recent-removals first.\\n /// @return the position of the empty leaf\\n function getEmptyLeafPosition() internal returns (uint256) {\\n uint256 rLeaf = rightmostLeaf;\\n bool spaceOnRight = (rLeaf + 1) < Constants.POOL_CAPACITY;\\n if (spaceOnRight) {\\n rightmostLeaf = rLeaf + 1;\\n return rLeaf;\\n } else {\\n uint256 emptyLeafCount = emptyLeaves.length;\\n require(emptyLeafCount > 0, \\\"Pool is full\\\");\\n uint256 emptyLeaf = emptyLeaves[emptyLeafCount - 1];\\n emptyLeaves.pop();\\n return emptyLeaf;\\n }\\n }\\n\\n /// @notice Gets the flagged leaf position for an operator.\\n /// @param operator the address of the operator\\n /// @return the leaf position of that operator\\n function getFlaggedLeafPosition(address operator)\\n internal\\n view\\n returns (uint256)\\n {\\n return flaggedLeafPosition[operator];\\n }\\n\\n /// @notice Gets the weight of a leaf at a particular position.\\n /// @param position the index of the leaf\\n /// @return the weight of the leaf at that position\\n function getLeafWeight(uint256 position) internal view returns (uint256) {\\n uint256 slot = position.slot();\\n uint256 parent = position.parent();\\n\\n // A leaf's weight information is stored a 32-bit slot in the branch layer\\n // directly above the leaf layer. To access it, we calculate that slot and\\n // parent position, and always know the hard-coded layer index.\\n uint256 node = branches[Constants.LEVELS][parent];\\n return node.getSlot(slot);\\n }\\n\\n /// @notice Picks a leaf given a random index.\\n /// @param index a number in `[0, _root.totalWeight())` used to decide\\n /// between leaves\\n /// @param _root the root of the tree\\n function pickWeightedLeaf(uint256 index, uint256 _root)\\n internal\\n view\\n returns (uint256 leafPosition)\\n {\\n uint256 currentIndex = index;\\n uint256 currentNode = _root;\\n uint256 currentPosition = 0;\\n uint256 currentSlot;\\n\\n require(index < currentNode.sumWeight(), \\\"Index exceeds weight\\\");\\n\\n // get root slot\\n (currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);\\n\\n // get slots from levels 2 to 7\\n for (uint256 level = 2; level <= Constants.LEVELS; level++) {\\n currentPosition = currentPosition.child(currentSlot);\\n currentNode = branches[level][currentPosition];\\n (currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);\\n }\\n\\n // get leaf position\\n leafPosition = currentPosition.child(currentSlot);\\n }\\n}\\n\",\"keccak256\":\"0x51daeca62ef52be78a1a9de4d2a1c5900c873165f59eda14d5965d7d7da90a03\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = _setInitializedVersion(1);\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original\\n * initialization step. This is essential to configure modules that are added through upgrades and that require\\n * initialization.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n */\\n modifier reinitializer(uint8 version) {\\n bool isTopLevelCall = _setInitializedVersion(version);\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(version);\\n }\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n */\\n function _disableInitializers() internal virtual {\\n _setInitializedVersion(type(uint8).max);\\n }\\n\\n function _setInitializedVersion(uint8 version) private returns (bool) {\\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\\n // inheritance patterns, but we only do this in the context of a constructor, and for the lowest level\\n // of initializers, because in other contexts the contract may have been reentered.\\n if (_initializing) {\\n require(\\n version == 1 && !AddressUpgradeable.isContract(address(this)),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n return false;\\n } else {\\n require(_initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n return true;\\n }\\n }\\n}\\n\",\"keccak256\":\"0x7454006cccb737612b00104d2f606d728e2818b778e7e55542f063c614ce46ba\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x55cf2bd9fc76704ddcdc19834cd288b7de00fc0f298a40ea16a954ae8991db2d\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary StringsUpgradeable {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x398d3323c1932a5986bf36be7c57593e121e69d5db5b6574b4ee0d031443de37\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../StringsUpgradeable.sol\\\";\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSAUpgradeable {\\n enum RecoverError {\\n NoError,\\n InvalidSignature,\\n InvalidSignatureLength,\\n InvalidSignatureS,\\n InvalidSignatureV\\n }\\n\\n function _throwError(RecoverError error) private pure {\\n if (error == RecoverError.NoError) {\\n return; // no error: do nothing\\n } else if (error == RecoverError.InvalidSignature) {\\n revert(\\\"ECDSA: invalid signature\\\");\\n } else if (error == RecoverError.InvalidSignatureLength) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n } else if (error == RecoverError.InvalidSignatureS) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n } else if (error == RecoverError.InvalidSignatureV) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature` or error string. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n *\\n * Documentation for signature generation:\\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\\n // Check the signature length\\n // - case 65: r,s,v signature (standard)\\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\\n if (signature.length == 65) {\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n return tryRecover(hash, v, r, s);\\n } else if (signature.length == 64) {\\n bytes32 r;\\n bytes32 vs;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n vs := mload(add(signature, 0x40))\\n }\\n return tryRecover(hash, r, vs);\\n } else {\\n return (address(0), RecoverError.InvalidSignatureLength);\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, signature);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\\n *\\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address, RecoverError) {\\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\\n uint8 v = uint8((uint256(vs) >> 255) + 27);\\n return tryRecover(hash, v, r, s);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\\n *\\n * _Available since v4.2._\\n */\\n function recover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address, RecoverError) {\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (301): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (302): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n return (address(0), RecoverError.InvalidSignatureS);\\n }\\n if (v != 27 && v != 28) {\\n return (address(0), RecoverError.InvalidSignatureV);\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n if (signer == address(0)) {\\n return (address(0), RecoverError.InvalidSignature);\\n }\\n\\n return (signer, RecoverError.NoError);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n */\\n function recover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from `s`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n\\\", StringsUpgradeable.toString(s.length), s));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Typed Data, created from a\\n * `domainSeparator` and a `structHash`. This produces hash corresponding\\n * to the one signed with the\\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\\n * JSON-RPC method as part of EIP-712.\\n *\\n * See {recover}.\\n */\\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19\\\\x01\\\", domainSeparator, structHash));\\n }\\n}\\n\",\"keccak256\":\"0x6602a65e0277f31f45cad4c7a15b024fd182f2f0e01eaa1954103b0d57848a27\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCastUpgradeable {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0xcec885ecdf113b4265ed0856972d7ff167bfeb3802604b18cbb782bf47ecc4ae\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0x24e0364e503a9bbde94c715d26573a76f14cd2a202d45f96f52134ab806b67b9\",\"license\":\"MIT\"},\"@openzeppelin/contracts/security/ReentrancyGuard.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Contract module that helps prevent reentrant calls to a function.\\n *\\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\\n * available, which can be applied to functions to make sure there are no nested\\n * (reentrant) calls to them.\\n *\\n * Note that because there is a single `nonReentrant` guard, functions marked as\\n * `nonReentrant` may not call one another. This can be worked around by making\\n * those functions `private`, and then adding `external` `nonReentrant` entry\\n * points to them.\\n *\\n * TIP: If you would like to learn more about reentrancy and alternative ways\\n * to protect against it, check out our blog post\\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\\n */\\nabstract contract ReentrancyGuard {\\n // Booleans are more expensive than uint256 or any type that takes up a full\\n // word because each write operation emits an extra SLOAD to first read the\\n // slot's contents, replace the bits taken up by the boolean, and then write\\n // back. This is the compiler's defense against contract upgrades and\\n // pointer aliasing, and it cannot be disabled.\\n\\n // The values being non-zero value makes deployment a bit more expensive,\\n // but in exchange the refund on every call to nonReentrant will be lower in\\n // amount. Since refunds are capped to a percentage of the total\\n // transaction's gas, it is best to keep them low in cases like this one, to\\n // increase the likelihood of the full refund coming into effect.\\n uint256 private constant _NOT_ENTERED = 1;\\n uint256 private constant _ENTERED = 2;\\n\\n uint256 private _status;\\n\\n constructor() {\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and making it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_status != _ENTERED, \\\"ReentrancyGuard: reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n _status = _ENTERED;\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n _status = _NOT_ENTERED;\\n }\\n}\\n\",\"keccak256\":\"0x0e9621f60b2faabe65549f7ed0f24e8853a45c1b7990d47e8160e523683f3935\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 amount\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x9750c6b834f7b43000631af5cc30001c5f547b3ceb3635488f140f60e897ea6b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x0d4de01fe5360c38b4ad2b0822a12722958428f5138a7ff47c1720eb6fa52bba\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x2ccf9d2313a313d41a791505f2b5abfdc62191b5d4334f7f7a82691c088a1c87\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x32c202bd28995dd20c4347b7c6467a6d3241c74c8ad3edcbb610cd9205916c45\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Strings.sol\\\";\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSA {\\n enum RecoverError {\\n NoError,\\n InvalidSignature,\\n InvalidSignatureLength,\\n InvalidSignatureS,\\n InvalidSignatureV\\n }\\n\\n function _throwError(RecoverError error) private pure {\\n if (error == RecoverError.NoError) {\\n return; // no error: do nothing\\n } else if (error == RecoverError.InvalidSignature) {\\n revert(\\\"ECDSA: invalid signature\\\");\\n } else if (error == RecoverError.InvalidSignatureLength) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n } else if (error == RecoverError.InvalidSignatureS) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n } else if (error == RecoverError.InvalidSignatureV) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature` or error string. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n *\\n * Documentation for signature generation:\\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\\n // Check the signature length\\n // - case 65: r,s,v signature (standard)\\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\\n if (signature.length == 65) {\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n return tryRecover(hash, v, r, s);\\n } else if (signature.length == 64) {\\n bytes32 r;\\n bytes32 vs;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n vs := mload(add(signature, 0x40))\\n }\\n return tryRecover(hash, r, vs);\\n } else {\\n return (address(0), RecoverError.InvalidSignatureLength);\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, signature);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\\n *\\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address, RecoverError) {\\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\\n uint8 v = uint8((uint256(vs) >> 255) + 27);\\n return tryRecover(hash, v, r, s);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\\n *\\n * _Available since v4.2._\\n */\\n function recover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address, RecoverError) {\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (301): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (302): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n return (address(0), RecoverError.InvalidSignatureS);\\n }\\n if (v != 27 && v != 28) {\\n return (address(0), RecoverError.InvalidSignatureV);\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n if (signer == address(0)) {\\n return (address(0), RecoverError.InvalidSignature);\\n }\\n\\n return (signer, RecoverError.NoError);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n */\\n function recover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from `s`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n\\\", Strings.toString(s.length), s));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Typed Data, created from a\\n * `domainSeparator` and a `structHash`. This produces hash corresponding\\n * to the one signed with the\\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\\n * JSON-RPC method as part of EIP-712.\\n *\\n * See {recover}.\\n */\\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19\\\\x01\\\", domainSeparator, structHash));\\n }\\n}\\n\",\"keccak256\":\"0x3c07f43e60e099b3b157243b3152722e73b80eeb7985c2cd73712828d7f7da29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/Math.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol)\\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.\\n return (a & b) + (a ^ b) / 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\",\"keccak256\":\"0xc995bddbca1ae19788db9f8b61e63385edd3fddf89693b612d5abd1a275974d2\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonce;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `spender` and `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `spender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(spender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(spender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonce[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(spender, recipient, amount);\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[spender] = spenderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(spender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x1e1bf4ec5c9d6fe70f6f834316482aeff3f122ff4ffaa7178099e7ae71a0b16d\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonce(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xdac9a5086c19a7128b505a7be1ab0ac1aa314f6989cb88d2417e9d7383f89fa9\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/GovernanceUtils.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\npragma solidity 0.8.17;\\n\\nlibrary GovernanceUtils {\\n /// @notice Reverts if the governance delay has not passed since\\n /// the change initiated time or if the change has not been\\n /// initiated.\\n /// @param changeInitiatedTimestamp The timestamp at which the change has\\n /// been initiated.\\n /// @param delay Governance delay.\\n function onlyAfterGovernanceDelay(\\n uint256 changeInitiatedTimestamp,\\n uint256 delay\\n ) internal view {\\n require(changeInitiatedTimestamp > 0, \\\"Change not initiated\\\");\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp - changeInitiatedTimestamp >= delay,\\n \\\"Governance delay has not elapsed\\\"\\n );\\n }\\n\\n /// @notice Gets the time remaining until the governable parameter update\\n /// can be committed.\\n /// @param changeInitiatedTimestamp Timestamp indicating the beginning of\\n /// the change.\\n /// @param delay Governance delay.\\n /// @return Remaining time in seconds.\\n function getRemainingGovernanceDelay(\\n uint256 changeInitiatedTimestamp,\\n uint256 delay\\n ) internal view returns (uint256) {\\n require(changeInitiatedTimestamp > 0, \\\"Change not initiated\\\");\\n /* solhint-disable-next-line not-rely-on-time */\\n uint256 elapsed = block.timestamp - changeInitiatedTimestamp;\\n if (elapsed >= delay) {\\n return 0;\\n } else {\\n return delay - elapsed;\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9d16501e1b7c368ced397fd2eff0ab1bd1db8d26cc3700d0b7740942ee3c3c31\",\"license\":\"GPL-3.0-only\"},\"contracts/bank/Bank.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IReceiveBalanceApproval.sol\\\";\\nimport \\\"../vault/IVault.sol\\\";\\n\\n/// @title Bitcoin Bank\\n/// @notice Bank is a central component tracking Bitcoin balances. Balances can\\n/// be transferred between balance owners, and balance owners can\\n/// approve their balances to be spent by others. Balances in the Bank\\n/// are updated for depositors who deposited their Bitcoin into the\\n/// Bridge and only the Bridge can increase balances.\\n/// @dev Bank is a governable contract and the Governance can upgrade the Bridge\\n/// address.\\ncontract Bank is Ownable {\\n address public bridge;\\n\\n /// @notice The balance of the given account in the Bank. Zero by default.\\n mapping(address => uint256) public balanceOf;\\n\\n /// @notice The remaining amount of balance a spender will be\\n /// allowed to transfer on behalf of an owner using\\n /// `transferBalanceFrom`. Zero by default.\\n mapping(address => mapping(address => uint256)) public allowance;\\n\\n /// @notice Returns the current nonce for an EIP2612 permission for the\\n /// provided balance owner to protect against replay attacks. Used\\n /// to construct an EIP2612 signature provided to the `permit`\\n /// function.\\n mapping(address => uint256) public nonces;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns an EIP2612 Permit message hash. Used to construct\\n /// an EIP2612 signature provided to the `permit` function.\\n bytes32 public constant PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n event BalanceTransferred(\\n address indexed from,\\n address indexed to,\\n uint256 amount\\n );\\n\\n event BalanceApproved(\\n address indexed owner,\\n address indexed spender,\\n uint256 amount\\n );\\n\\n event BalanceIncreased(address indexed owner, uint256 amount);\\n\\n event BalanceDecreased(address indexed owner, uint256 amount);\\n\\n event BridgeUpdated(address newBridge);\\n\\n modifier onlyBridge() {\\n require(msg.sender == address(bridge), \\\"Caller is not the bridge\\\");\\n _;\\n }\\n\\n constructor() {\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Allows the Governance to upgrade the Bridge address.\\n /// @dev The function does not implement any governance delay and does not\\n /// check the status of the Bridge. The Governance implementation needs\\n /// to ensure all requirements for the upgrade are satisfied before\\n /// executing this function.\\n /// Requirements:\\n /// - The new Bridge address must not be zero.\\n /// @param _bridge The new Bridge address.\\n function updateBridge(address _bridge) external onlyOwner {\\n require(_bridge != address(0), \\\"Bridge address must not be 0x0\\\");\\n bridge = _bridge;\\n emit BridgeUpdated(_bridge);\\n }\\n\\n /// @notice Moves the given `amount` of balance from the caller to\\n /// `recipient`.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n /// @param recipient The recipient of the balance.\\n /// @param amount The amount of the balance transferred.\\n function transferBalance(address recipient, uint256 amount) external {\\n _transferBalance(msg.sender, recipient, amount);\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// balance. Does not allow updating an existing allowance to\\n /// a value that is non-zero to avoid someone using both the old and\\n /// the new allowance by unfortunate transaction ordering. To update\\n /// an allowance to a non-zero value please set it to zero first or\\n /// use `increaseBalanceAllowance` or `decreaseBalanceAllowance` for\\n /// an atomic update.\\n /// @dev If the `amount` is set to `type(uint256).max`,\\n /// `transferBalanceFrom` will not reduce an allowance.\\n /// @param spender The address that will be allowed to spend the balance.\\n /// @param amount The amount the spender is allowed to spend.\\n function approveBalance(address spender, uint256 amount) external {\\n require(\\n amount == 0 || allowance[msg.sender][spender] == 0,\\n \\\"Non-atomic allowance change not allowed\\\"\\n );\\n _approveBalance(msg.sender, spender, amount);\\n }\\n\\n /// @notice Sets the `amount` as an allowance of a smart contract `spender`\\n /// over the caller's balance and calls the `spender` via\\n /// `receiveBalanceApproval`.\\n /// @dev If the `amount` is set to `type(uint256).max`, the potential\\n /// `transferBalanceFrom` executed in `receiveBalanceApproval` of\\n /// `spender` will not reduce an allowance. Beware that changing an\\n /// allowance with this function brings the risk that `spender` may use\\n /// both the old and the new allowance by unfortunate transaction\\n /// ordering. Please use `increaseBalanceAllowance` and\\n /// `decreaseBalanceAllowance` to eliminate the risk.\\n /// @param spender The smart contract that will be allowed to spend the\\n /// balance.\\n /// @param amount The amount the spender contract is allowed to spend.\\n /// @param extraData Extra data passed to the `spender` contract via\\n /// `receiveBalanceApproval` call.\\n function approveBalanceAndCall(\\n address spender,\\n uint256 amount,\\n bytes calldata extraData\\n ) external {\\n _approveBalance(msg.sender, spender, amount);\\n IReceiveBalanceApproval(spender).receiveBalanceApproval(\\n msg.sender,\\n amount,\\n extraData\\n );\\n }\\n\\n /// @notice Atomically increases the caller's balance allowance granted to\\n /// `spender` by the given `addedValue`.\\n /// @param spender The spender address for which the allowance is increased.\\n /// @param addedValue The amount by which the allowance is increased.\\n function increaseBalanceAllowance(address spender, uint256 addedValue)\\n external\\n {\\n _approveBalance(\\n msg.sender,\\n spender,\\n allowance[msg.sender][spender] + addedValue\\n );\\n }\\n\\n /// @notice Atomically decreases the caller's balance allowance granted to\\n /// `spender` by the given `subtractedValue`.\\n /// @dev Requirements:\\n /// - `spender` must not be the zero address,\\n /// - the current allowance for `spender` must not be lower than\\n /// the `subtractedValue`.\\n /// @param spender The spender address for which the allowance is decreased.\\n /// @param subtractedValue The amount by which the allowance is decreased.\\n function decreaseBalanceAllowance(address spender, uint256 subtractedValue)\\n external\\n {\\n uint256 currentAllowance = allowance[msg.sender][spender];\\n require(\\n currentAllowance >= subtractedValue,\\n \\\"Can not decrease balance allowance below zero\\\"\\n );\\n unchecked {\\n _approveBalance(\\n msg.sender,\\n spender,\\n currentAllowance - subtractedValue\\n );\\n }\\n }\\n\\n /// @notice Moves `amount` of balance from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have an allowance for `spender`'s balance of at\\n /// least `amount`.\\n /// @param spender The address from which the balance is transferred.\\n /// @param recipient The address to which the balance is transferred.\\n /// @param amount The amount of balance that is transferred.\\n function transferBalanceFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n unchecked {\\n _approveBalance(spender, msg.sender, currentAllowance - amount);\\n }\\n }\\n _transferBalance(spender, recipient, amount);\\n }\\n\\n /// @notice An EIP2612 approval made with secp256k1 signature. Users can\\n /// authorize a transfer of their balance with a signature\\n /// conforming to the EIP712 standard, rather than an on-chain\\n /// transaction from their address. Anyone can submit this signature\\n /// on the user's behalf by calling the `permit` function, paying\\n /// gas fees, and possibly performing other actions in the same\\n /// transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferBalanceFrom` will not\\n /// reduce an allowance. Beware that changing an allowance with this\\n /// function brings the risk that someone may use both the old and the\\n /// new allowance by unfortunate transaction ordering. Please use\\n /// `increaseBalanceAllowance` and `decreaseBalanceAllowance` to\\n /// eliminate the risk.\\n /// @param owner The balance owner who signed the permission.\\n /// @param spender The address that will be allowed to spend the balance.\\n /// @param amount The amount the spender is allowed to spend.\\n /// @param deadline The UNIX time until which the permit is valid.\\n /// @param v V part of the permit signature.\\n /// @param r R part of the permit signature.\\n /// @param s S part of the permit signature.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonces[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approveBalance(owner, spender, amount);\\n }\\n\\n /// @notice Increases balances of the provided `recipients` by the provided\\n /// `amounts`. Can only be called by the Bridge.\\n /// @dev Requirements:\\n /// - length of `recipients` and `amounts` must be the same,\\n /// - none of `recipients` addresses must point to the Bank.\\n /// @param recipients Balance increase recipients.\\n /// @param amounts Amounts by which balances are increased.\\n function increaseBalances(\\n address[] calldata recipients,\\n uint256[] calldata amounts\\n ) external onlyBridge {\\n require(\\n recipients.length == amounts.length,\\n \\\"Arrays must have the same length\\\"\\n );\\n for (uint256 i = 0; i < recipients.length; i++) {\\n _increaseBalance(recipients[i], amounts[i]);\\n }\\n }\\n\\n /// @notice Increases balance of the provided `recipient` by the provided\\n /// `amount`. Can only be called by the Bridge.\\n /// @dev Requirements:\\n /// - `recipient` address must not point to the Bank.\\n /// @param recipient Balance increase recipient.\\n /// @param amount Amount by which the balance is increased.\\n function increaseBalance(address recipient, uint256 amount)\\n external\\n onlyBridge\\n {\\n _increaseBalance(recipient, amount);\\n }\\n\\n /// @notice Increases the given smart contract `vault`'s balance and\\n /// notifies the `vault` contract about it.\\n /// Can be called only by the Bridge.\\n /// @dev Requirements:\\n /// - `vault` must implement `IVault` interface,\\n /// - length of `recipients` and `amounts` must be the same.\\n /// @param vault Address of `IVault` recipient contract.\\n /// @param recipients Balance increase recipients.\\n /// @param amounts Amounts by which balances are increased.\\n function increaseBalanceAndCall(\\n address vault,\\n address[] calldata recipients,\\n uint256[] calldata amounts\\n ) external onlyBridge {\\n require(\\n recipients.length == amounts.length,\\n \\\"Arrays must have the same length\\\"\\n );\\n uint256 totalAmount = 0;\\n for (uint256 i = 0; i < amounts.length; i++) {\\n totalAmount += amounts[i];\\n }\\n _increaseBalance(vault, totalAmount);\\n IVault(vault).receiveBalanceIncrease(recipients, amounts);\\n }\\n\\n /// @notice Decreases caller's balance by the provided `amount`. There is no\\n /// way to restore the balance so do not call this function unless\\n /// you really know what you are doing!\\n /// @dev Requirements:\\n /// - The caller must have a balance of at least `amount`.\\n /// @param amount The amount by which the balance is decreased.\\n function decreaseBalance(uint256 amount) external {\\n balanceOf[msg.sender] -= amount;\\n emit BalanceDecreased(msg.sender, amount);\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with `TBTC Bank` as\\n /// a signing domain and Bank contract as a verifying contract.\\n /// Used to construct an EIP2612 signature provided to the `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n function _increaseBalance(address recipient, uint256 amount) internal {\\n require(\\n recipient != address(this),\\n \\\"Can not increase balance for Bank\\\"\\n );\\n balanceOf[recipient] += amount;\\n emit BalanceIncreased(recipient, amount);\\n }\\n\\n function _transferBalance(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(\\n recipient != address(0),\\n \\\"Can not transfer to the zero address\\\"\\n );\\n require(\\n recipient != address(this),\\n \\\"Can not transfer to the Bank address\\\"\\n );\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n unchecked {\\n balanceOf[spender] = spenderBalance - amount;\\n }\\n balanceOf[recipient] += amount;\\n emit BalanceTransferred(spender, recipient, amount);\\n }\\n\\n function _approveBalance(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Can not approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit BalanceApproved(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(\\\"TBTC Bank\\\")),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x318005485bc8fb8a8fb6091bc4a3ca0e304693d8b372b61835bed2f1f735faf7\",\"license\":\"GPL-3.0-only\"},\"contracts/bank/IReceiveBalanceApproval.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\n/// @title IReceiveBalanceApproval\\n/// @notice `IReceiveBalanceApproval` is an interface for a smart contract\\n/// consuming Bank balances approved to them in the same transaction by\\n/// other contracts or externally owned accounts (EOA).\\ninterface IReceiveBalanceApproval {\\n /// @notice Called by the Bank in `approveBalanceAndCall` function after\\n /// the balance `owner` approved `amount` of their balance in the\\n /// Bank for the contract. This way, the depositor can approve\\n /// balance and call the contract to use the approved balance in\\n /// a single transaction.\\n /// @param owner Address of the Bank balance owner who approved their\\n /// balance to be used by the contract.\\n /// @param amount The amount of the Bank balance approved by the owner\\n /// to be used by the contract.\\n /// @param extraData The `extraData` passed to `Bank.approveBalanceAndCall`.\\n /// @dev The implementation must ensure this function can only be called\\n /// by the Bank. The Bank does _not_ guarantee that the `amount`\\n /// approved by the `owner` currently exists on their balance. That is,\\n /// the `owner` could approve more balance than they currently have.\\n /// This works the same as `Bank.approve` function. The contract must\\n /// ensure the actual balance is checked before performing any action\\n /// based on it.\\n function receiveBalanceApproval(\\n address owner,\\n uint256 amount,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x864f29d54d9d672348520b1f46bbce786994e07d86032987e4374a267a345c2b\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/BitcoinTx.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 pure\\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 // The output consists of an 8-byte value and a variable length script.\\n // To extract just the script, we ignore the first 8 bytes.\\n uint256 scriptLen = output.length - 8;\\n\\n // The P2PKH script is 26 bytes long.\\n // The P2WPKH script is 23 bytes long.\\n // A valid script must have one of these lengths,\\n // and we can identify the expected script type by the length.\\n require(\\n scriptLen == 26 || scriptLen == 23,\\n \\\"Output must be P2PKH or P2WPKH\\\"\\n );\\n\\n if (scriptLen == 26) {\\n // Compare to the expected P2PKH script.\\n bytes26 script = bytes26(output.slice32(8));\\n\\n require(\\n script == makeP2PKHScript(pubKeyHash),\\n \\\"Invalid P2PKH script\\\"\\n );\\n }\\n\\n if (scriptLen == 23) {\\n // Compare to the expected P2WPKH script.\\n bytes23 script = bytes23(output.slice32(8));\\n\\n require(\\n script == makeP2WPKHScript(pubKeyHash),\\n \\\"Invalid P2WPKH script\\\"\\n );\\n }\\n\\n return pubKeyHash;\\n }\\n\\n /// @notice Build the P2PKH script from the given public key hash.\\n /// @param pubKeyHash The 20-byte public key hash.\\n /// @return The P2PKH script.\\n /// @dev The P2PKH script has the following byte format:\\n /// <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 function makeP2PKHScript(bytes20 pubKeyHash)\\n internal\\n pure\\n returns (bytes26)\\n {\\n bytes26 P2PKHScriptMask = hex\\\"1976a914000000000000000000000000000000000000000088ac\\\";\\n\\n return ((bytes26(pubKeyHash) >> 32) | P2PKHScriptMask);\\n }\\n\\n /// @notice Build the P2WPKH script from the given public key hash.\\n /// @param pubKeyHash The 20-byte public key hash.\\n /// @return The P2WPKH script.\\n /// @dev The P2WPKH script 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 function makeP2WPKHScript(bytes20 pubKeyHash)\\n internal\\n pure\\n returns (bytes23)\\n {\\n bytes23 P2WPKHScriptMask = hex\\\"1600140000000000000000000000000000000000000000\\\";\\n\\n return ((bytes23(pubKeyHash) >> 24) | P2WPKHScriptMask);\\n }\\n}\\n\",\"keccak256\":\"0xb087cb5b364bcdcca63772a442c630e858c90555fc691521a212c068b89120a5\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Bridge.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@keep-network/random-beacon/contracts/Governable.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/ReimbursementPool.sol\\\";\\nimport {IWalletOwner as EcdsaWalletOwner} from \\\"@keep-network/ecdsa/contracts/api/IWalletOwner.sol\\\";\\n\\nimport \\\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\\\";\\nimport \\\"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol\\\";\\n\\nimport \\\"./IRelay.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Deposit.sol\\\";\\nimport \\\"./DepositSweep.sol\\\";\\nimport \\\"./Redemption.sol\\\";\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./EcdsaLib.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\nimport \\\"./Fraud.sol\\\";\\nimport \\\"./MovingFunds.sol\\\";\\n\\nimport \\\"../bank/IReceiveBalanceApproval.sol\\\";\\nimport \\\"../bank/Bank.sol\\\";\\n\\n/// @title Bitcoin Bridge\\n/// @notice Bridge manages BTC deposit and redemption flow and is increasing and\\n/// decreasing balances in the Bank as a result of BTC deposit and\\n/// redemption operations performed by depositors and redeemers.\\n///\\n/// Depositors send BTC funds to the most recently created off-chain\\n/// ECDSA wallet of the bridge using pay-to-script-hash (P2SH) or\\n/// pay-to-witness-script-hash (P2WSH) containing hashed information\\n/// about the depositor\\u2019s Ethereum address. Then, the depositor reveals\\n/// their Ethereum address along with their deposit blinding factor,\\n/// refund public key hash and refund locktime to the Bridge on Ethereum\\n/// chain. The off-chain ECDSA wallet listens for these sorts of\\n/// messages and when it gets one, it checks the Bitcoin network to make\\n/// sure the deposit lines up. If it does, the off-chain ECDSA wallet\\n/// may decide to pick the deposit transaction for sweeping, and when\\n/// the sweep operation is confirmed on the Bitcoin network, the ECDSA\\n/// wallet informs the Bridge about the sweep increasing appropriate\\n/// balances in the Bank.\\n/// @dev Bridge is an upgradeable component of the Bank. The order of\\n/// functionalities in this contract is: deposit, sweep, redemption,\\n/// moving funds, wallet lifecycle, frauds, parameters.\\ncontract Bridge is\\n Governable,\\n EcdsaWalletOwner,\\n Initializable,\\n IReceiveBalanceApproval\\n{\\n using BridgeState for BridgeState.Storage;\\n using Deposit for BridgeState.Storage;\\n using DepositSweep for BridgeState.Storage;\\n using Redemption for BridgeState.Storage;\\n using MovingFunds for BridgeState.Storage;\\n using Wallets for BridgeState.Storage;\\n using Fraud for BridgeState.Storage;\\n\\n BridgeState.Storage internal self;\\n\\n event DepositRevealed(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex,\\n address indexed depositor,\\n uint64 amount,\\n bytes8 blindingFactor,\\n bytes20 indexed walletPubKeyHash,\\n bytes20 refundPubKeyHash,\\n bytes4 refundLocktime,\\n address vault\\n );\\n\\n event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);\\n\\n event RedemptionRequested(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript,\\n address indexed redeemer,\\n uint64 requestedAmount,\\n uint64 treasuryFee,\\n uint64 txMaxFee\\n );\\n\\n event RedemptionsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 redemptionTxHash\\n );\\n\\n event RedemptionTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript\\n );\\n\\n event WalletMovingFunds(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event MovingFundsCommitmentSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes20[] targetWallets,\\n address submitter\\n );\\n\\n event MovingFundsTimeoutReset(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash\\n );\\n\\n event MovingFundsTimedOut(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsBelowDustReported(bytes20 indexed walletPubKeyHash);\\n\\n event MovedFundsSwept(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sweepTxHash\\n );\\n\\n event MovedFundsSweepTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex\\n );\\n\\n event NewWalletRequested();\\n\\n event NewWalletRegistered(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosing(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosed(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletTerminated(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event FraudChallengeSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n );\\n\\n event FraudChallengeDefeated(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash\\n );\\n\\n event FraudChallengeDefeatTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash\\n );\\n\\n event VaultStatusUpdated(address indexed vault, bool isTrusted);\\n\\n event SpvMaintainerStatusUpdated(\\n address indexed spvMaintainer,\\n bool isTrusted\\n );\\n\\n event DepositParametersUpdated(\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n );\\n\\n event RedemptionParametersUpdated(\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n );\\n\\n event MovingFundsParametersUpdated(\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n\\n event WalletParametersUpdated(\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n );\\n\\n event FraudParametersUpdated(\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n );\\n\\n event TreasuryUpdated(address treasury);\\n\\n modifier onlySpvMaintainer() {\\n require(\\n self.isSpvMaintainer[msg.sender],\\n \\\"Caller is not SPV maintainer\\\"\\n );\\n _;\\n }\\n\\n /// @custom:oz-upgrades-unsafe-allow constructor\\n constructor() {\\n _disableInitializers();\\n }\\n\\n /// @dev Initializes upgradable contract on deployment.\\n /// @param _bank Address of the Bank the Bridge belongs to.\\n /// @param _relay Address of the Bitcoin relay providing the current Bitcoin\\n /// network difficulty.\\n /// @param _treasury Address where the deposit and redemption treasury fees\\n /// will be sent to.\\n /// @param _ecdsaWalletRegistry Address of the ECDSA Wallet Registry contract.\\n /// @param _reimbursementPool Address of the Reimbursement Pool contract.\\n /// @param _txProofDifficultyFactor The number of confirmations on the Bitcoin\\n /// chain required to successfully evaluate an SPV proof.\\n function initialize(\\n address _bank,\\n address _relay,\\n address _treasury,\\n address _ecdsaWalletRegistry,\\n address payable _reimbursementPool,\\n uint96 _txProofDifficultyFactor\\n ) external initializer {\\n require(_bank != address(0), \\\"Bank address cannot be zero\\\");\\n self.bank = Bank(_bank);\\n\\n require(_relay != address(0), \\\"Relay address cannot be zero\\\");\\n self.relay = IRelay(_relay);\\n\\n require(\\n _ecdsaWalletRegistry != address(0),\\n \\\"ECDSA Wallet Registry address cannot be zero\\\"\\n );\\n self.ecdsaWalletRegistry = EcdsaWalletRegistry(_ecdsaWalletRegistry);\\n\\n require(\\n _reimbursementPool != address(0),\\n \\\"Reimbursement Pool address cannot be zero\\\"\\n );\\n self.reimbursementPool = ReimbursementPool(_reimbursementPool);\\n\\n require(_treasury != address(0), \\\"Treasury address cannot be zero\\\");\\n self.treasury = _treasury;\\n\\n self.txProofDifficultyFactor = _txProofDifficultyFactor;\\n\\n //\\n // All parameters set in the constructor are initial ones, used at the\\n // moment contracts were deployed for the first time. Parameters are\\n // governable and values assigned in the constructor do not need to\\n // reflect the current ones. Keep in mind the initial parameters are\\n // pretty forgiving and valid only for the early stage of the network.\\n //\\n\\n self.depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC\\n self.depositTxMaxFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.depositRevealAheadPeriod = 15 days;\\n self.depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005\\n self.redemptionDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC\\n self.redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005\\n self.redemptionTxMaxFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.redemptionTxMaxTotalFee = 1000000; // 1000000 satoshi = 0.01 BTC\\n self.redemptionTimeout = 5 days;\\n self.redemptionTimeoutSlashingAmount = 100 * 1e18; // 100 T\\n self.redemptionTimeoutNotifierRewardMultiplier = 100; // 100%\\n self.movingFundsTxMaxTotalFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.movingFundsDustThreshold = 200000; // 200000 satoshi = 0.002 BTC\\n self.movingFundsTimeoutResetDelay = 6 days;\\n self.movingFundsTimeout = 7 days;\\n self.movingFundsTimeoutSlashingAmount = 100 * 1e18; // 100 T\\n self.movingFundsTimeoutNotifierRewardMultiplier = 100; //100%\\n self.movingFundsCommitmentGasOffset = 15000;\\n self.movedFundsSweepTxMaxTotalFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.movedFundsSweepTimeout = 7 days;\\n self.movedFundsSweepTimeoutSlashingAmount = 100 * 1e18; // 100 T\\n self.movedFundsSweepTimeoutNotifierRewardMultiplier = 100; //100%\\n self.fraudChallengeDepositAmount = 5 ether;\\n self.fraudChallengeDefeatTimeout = 7 days;\\n self.fraudSlashingAmount = 100 * 1e18; // 100 T\\n self.fraudNotifierRewardMultiplier = 100; // 100%\\n self.walletCreationPeriod = 1 weeks;\\n self.walletCreationMinBtcBalance = 1e8; // 1 BTC\\n self.walletCreationMaxBtcBalance = 100e8; // 100 BTC\\n self.walletClosureMinBtcBalance = 5 * 1e7; // 0.5 BTC\\n self.walletMaxAge = 26 weeks; // ~6 months\\n self.walletMaxBtcTransfer = 10e8; // 10 BTC\\n self.walletClosingPeriod = 40 days;\\n\\n _transferGovernance(msg.sender);\\n }\\n\\n /// @notice Used by the depositor to reveal information about their P2(W)SH\\n /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain\\n /// wallet listens for revealed deposit events and may decide to\\n /// include the revealed deposit in the next executed sweep.\\n /// Information about the Bitcoin deposit can be revealed before or\\n /// after the Bitcoin transaction with P2(W)SH deposit is mined on\\n /// the Bitcoin chain. Worth noting, the gas cost of this function\\n /// scales with the number of P2(W)SH transaction inputs and\\n /// outputs. The deposit may be routed to one of the trusted vaults.\\n /// When a deposit is routed to a vault, vault gets notified when\\n /// the deposit gets swept and it may execute the appropriate action.\\n /// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`.\\n /// @param reveal Deposit reveal data, see `RevealInfo struct.\\n /// @dev Requirements:\\n /// - This function must be called by the same Ethereum address as the\\n /// one used in the P2(W)SH BTC deposit transaction as a depositor,\\n /// - `reveal.walletPubKeyHash` must identify a `Live` wallet,\\n /// - `reveal.vault` must be 0x0 or point to a trusted vault,\\n /// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH\\n /// output of the BTC deposit transaction,\\n /// - `reveal.blindingFactor` must be the blinding factor used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundPubKeyHash` must be the refund pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundLocktime` must be the refund locktime used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`\\n /// can be revealed only one time.\\n ///\\n /// If any of these requirements is not met, the wallet _must_ refuse\\n /// to sweep the deposit and the depositor has to wait until the\\n /// deposit script unlocks to receive their BTC back.\\n function revealDeposit(\\n BitcoinTx.Info calldata fundingTx,\\n Deposit.DepositRevealInfo calldata reveal\\n ) external {\\n self.revealDeposit(fundingTx, reveal);\\n }\\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 /// @param vault Optional address of the vault where all swept deposits\\n /// should be routed to. All deposits swept as part of the transaction\\n /// must have their `vault` parameters set to the same address.\\n /// If this parameter is set to an address of a trusted vault, swept\\n /// deposits are routed to that vault.\\n /// If this parameter is set to the zero address or to an address\\n /// of a non-trusted vault, swept deposits are not routed to a\\n /// vault but depositors' balances are increased in the Bank\\n /// individually.\\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 /// - All revealed deposits that are swept by `sweepTx` must have\\n /// their `vault` parameters set to the same address as the address\\n /// passed in the `vault` function parameter,\\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 BitcoinTx.Info calldata sweepTx,\\n BitcoinTx.Proof calldata sweepProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n address vault\\n ) external onlySpvMaintainer {\\n self.submitDepositSweepProof(sweepTx, sweepProof, mainUtxo, vault);\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script. Handles the\\n /// simplest case in which the redeemer's balance is decreased in\\n /// the Bank.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to process the request,\\n /// - Redeemer must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes calldata redeemerOutputScript,\\n uint64 amount\\n ) external {\\n self.requestRedemption(\\n walletPubKeyHash,\\n mainUtxo,\\n msg.sender,\\n redeemerOutputScript,\\n amount\\n );\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script. Used by\\n /// `Bank.approveBalanceAndCall`. Can handle more complex cases\\n /// where balance owner may be someone else than the redeemer.\\n /// For example, vault redeeming its balance for some depositor.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @param redemptionData ABI-encoded redemption data:\\n /// [\\n /// address redeemer,\\n /// bytes20 walletPubKeyHash,\\n /// bytes32 mainUtxoTxHash,\\n /// uint32 mainUtxoTxOutputIndex,\\n /// uint64 mainUtxoTxOutputValue,\\n /// bytes redeemerOutputScript\\n /// ]\\n ///\\n /// - redeemer: The Ethereum address of the redeemer who will be able\\n /// to claim Bank balance if anything goes wrong during the redemption.\\n /// In the most basic case, when someone redeems their balance\\n /// from the Bank, `balanceOwner` is the same as `redeemer`.\\n /// However, when a Vault is redeeming part of its balance for some\\n /// redeemer address (for example, someone who has earlier deposited\\n /// into that Vault), `balanceOwner` is the Vault, and `redeemer` is\\n /// the address for which the vault is redeeming its balance to,\\n /// - walletPubKeyHash: The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key),\\n /// - mainUtxoTxHash: Data of the wallet's main UTXO TX hash, as\\n /// currently known on the Ethereum chain,\\n /// - mainUtxoTxOutputIndex: Data of the wallet's main UTXO output\\n /// index, as currently known on Ethereum chain,\\n /// - mainUtxoTxOutputValue: Data of the wallet's main UTXO output\\n /// value, as currently known on Ethereum chain,\\n /// - redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @dev Requirements:\\n /// - The caller must be the Bank,\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to process the request.\\n ///\\n /// Note on upgradeability:\\n /// Bridge is an upgradeable contract deployed behind\\n /// a TransparentUpgradeableProxy. Accepting redemption data as bytes\\n /// provides great flexibility. The Bridge is just like any other\\n /// contract with a balance approved in the Bank and can be upgraded\\n /// to another version without being bound to a particular interface\\n /// forever. This flexibility comes with the cost - developers\\n /// integrating their vaults and dApps with `Bridge` using\\n /// `approveBalanceAndCall` need to pay extra attention to\\n /// `redemptionData` and adjust the code in case the expected structure\\n /// of `redemptionData` changes.\\n function receiveBalanceApproval(\\n address balanceOwner,\\n uint256 amount,\\n bytes calldata redemptionData\\n ) external override {\\n require(msg.sender == address(self.bank), \\\"Caller is not the bank\\\");\\n\\n self.requestRedemption(\\n balanceOwner,\\n SafeCastUpgradeable.toUint64(amount),\\n redemptionData\\n );\\n }\\n\\n /// @notice Used by the wallet to prove the BTC redemption transaction\\n /// and to make the necessary bookkeeping. Redemption is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function is performing Bank balance updates by burning\\n /// the total redeemed Bitcoin amount from Bridge balance and\\n /// transferring the treasury fee sum to the treasury address.\\n ///\\n /// It is possible to prove the given redemption only one time.\\n /// @param redemptionTx Bitcoin redemption transaction data.\\n /// @param redemptionProof Bitcoin redemption proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @dev Requirements:\\n /// - `redemptionTx` 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 `redemptionTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs handling existing pending\\n /// redemption requests or pointing to reported timed out requests.\\n /// There can be also 1 optional output representing the\\n /// change and pointing back to the 20-byte wallet public key hash.\\n /// The change should be always present if the redeemed value sum\\n /// is lower than the total wallet's BTC balance,\\n /// - `redemptionProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input.\\n /// Other remarks:\\n /// - Putting the change output as the first transaction output can\\n /// save some gas because the output processing loop begins each\\n /// iteration by checking whether the given output is the change\\n /// thus uses some gas for making the comparison. Once the change\\n /// is identified, that check is omitted in further iterations.\\n function submitRedemptionProof(\\n BitcoinTx.Info calldata redemptionTx,\\n BitcoinTx.Proof calldata redemptionProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external onlySpvMaintainer {\\n self.submitRedemptionProof(\\n redemptionTx,\\n redemptionProof,\\n mainUtxo,\\n walletPubKeyHash\\n );\\n }\\n\\n /// @notice Notifies that there is a pending redemption request associated\\n /// with the given wallet, that has timed out. The redemption\\n /// request is identified by the key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The results of calling this function:\\n /// - The pending redemptions value for the wallet will be decreased\\n /// by the requested amount (minus treasury fee),\\n /// - The tokens taken from the redeemer on redemption request will\\n /// be returned to the redeemer,\\n /// - The request will be moved from pending redemptions to\\n /// timed-out redemptions,\\n /// - If the state of the wallet is `Live` or `MovingFunds`, the\\n /// wallet operators will be slashed and the notifier will be\\n /// rewarded,\\n /// - If the state of wallet is `Live`, the wallet will be closed or\\n /// marked as `MovingFunds` (depending on the presence or absence\\n /// of the wallet's main UTXO) and the wallet will no longer be\\n /// marked as the active wallet (if it was marked as such).\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH).\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Terminated state,\\n /// - The redemption request identified by `walletPubKeyHash` and\\n /// `redeemerOutputScript` must exist,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time defined by `redemptionTimeout` must have\\n /// passed since the redemption was requested (the request must be\\n /// timed-out).\\n function notifyRedemptionTimeout(\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs,\\n bytes calldata redeemerOutputScript\\n ) external {\\n self.notifyRedemptionTimeout(\\n walletPubKeyHash,\\n walletMembersIDs,\\n redeemerOutputScript\\n );\\n }\\n\\n /// @notice Submits the moving funds target wallets commitment.\\n /// Once all requirements are met, that function registers the\\n /// target wallets commitment and opens the way for moving funds\\n /// proof submission.\\n /// The caller is reimbursed for the transaction costs.\\n /// @param walletPubKeyHash 20-byte public key hash of the source wallet.\\n /// @param walletMainUtxo Data of the source wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @param walletMembersIDs Identifiers of the source wallet signing group\\n /// members.\\n /// @param walletMemberIndex Position of the caller in the source wallet\\n /// signing group members list.\\n /// @param targetWallets List of 20-byte public key hashes of the target\\n /// wallets that the source wallet commits to move the funds to.\\n /// @dev Requirements:\\n /// - The source wallet must be in the MovingFunds state,\\n /// - The source wallet must not have pending redemption requests,\\n /// - The source wallet must not have pending moved funds sweep requests,\\n /// - The source wallet must not have submitted its commitment already,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given source wallet in the ECDSA registry. Those IDs are\\n /// not directly stored in the contract for gas efficiency purposes\\n /// but they can be read from appropriate `DkgResultSubmitted`\\n /// and `DkgResultApproved` events,\\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],\\n /// - The caller must be the member of the source wallet signing group\\n /// at the position indicated by `walletMemberIndex` parameter,\\n /// - The `walletMainUtxo` components must point to the recent main\\n /// UTXO of the source wallet, as currently known on the Ethereum\\n /// chain,\\n /// - Source wallet BTC balance must be greater than zero,\\n /// - At least one Live wallet must exist in the system,\\n /// - Submitted target wallets count must match the expected count\\n /// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`\\n /// where `N > 0`,\\n /// - Each target wallet must be not equal to the source wallet,\\n /// - Each target wallet must follow the expected order i.e. all\\n /// target wallets 20-byte public key hashes represented as numbers\\n /// must form a strictly increasing sequence without duplicates,\\n /// - Each target wallet must be in Live state.\\n function submitMovingFundsCommitment(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo,\\n uint32[] calldata walletMembersIDs,\\n uint256 walletMemberIndex,\\n bytes20[] calldata targetWallets\\n ) external {\\n uint256 gasStart = gasleft();\\n\\n self.submitMovingFundsCommitment(\\n walletPubKeyHash,\\n walletMainUtxo,\\n walletMembersIDs,\\n walletMemberIndex,\\n targetWallets\\n );\\n\\n self.reimbursementPool.refund(\\n (gasStart - gasleft()) + self.movingFundsCommitmentGasOffset,\\n msg.sender\\n );\\n }\\n\\n /// @notice Resets the moving funds timeout for the given wallet if the\\n /// target wallet commitment cannot be submitted due to a lack\\n /// of live wallets in the system.\\n /// @param walletPubKeyHash 20-byte public key hash of the moving funds wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The target wallets commitment must not be already submitted for\\n /// the given moving funds wallet,\\n /// - Live wallets count must be zero,\\n /// - The moving funds timeout reset delay must be elapsed.\\n function resetMovingFundsTimeout(bytes20 walletPubKeyHash) external {\\n self.resetMovingFundsTimeout(walletPubKeyHash);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moving funds transaction\\n /// and to make the necessary state changes. Moving funds is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the moving funds transaction structure\\n /// by checking if it actually spends the main UTXO of the declared\\n /// wallet and locks the value on the pre-committed target wallets\\n /// using a reasonable transaction fee. If all preconditions are\\n /// met, this functions closes the source wallet.\\n ///\\n /// It is possible to prove the given moving funds transaction only\\n /// one time.\\n /// @param movingFundsTx Bitcoin moving funds transaction data.\\n /// @param movingFundsProof Bitcoin moving funds proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet\\n /// which performed the moving funds transaction.\\n /// @dev Requirements:\\n /// - `movingFundsTx` 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 `movingFundsTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs corresponding to the\\n /// pre-committed target wallets. Outputs must be ordered in the\\n /// same way as their corresponding target wallets are ordered\\n /// within the target wallets commitment,\\n /// - `movingFundsProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input,\\n /// - The wallet that `walletPubKeyHash` points to must be in the\\n /// MovingFunds state,\\n /// - The target wallets commitment must be submitted by the wallet\\n /// that `walletPubKeyHash` points to,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movingFundsTxMaxTotalFee` governable parameter.\\n function submitMovingFundsProof(\\n BitcoinTx.Info calldata movingFundsTx,\\n BitcoinTx.Proof calldata movingFundsProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external onlySpvMaintainer {\\n self.submitMovingFundsProof(\\n movingFundsTx,\\n movingFundsProof,\\n mainUtxo,\\n walletPubKeyHash\\n );\\n }\\n\\n /// @notice Notifies about a timed out moving funds process. Terminates\\n /// the wallet and slashes signing group members as a result.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The moving funds timeout must be actually exceeded,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovingFundsTimeout(\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n self.notifyMovingFundsTimeout(walletPubKeyHash, walletMembersIDs);\\n }\\n\\n /// @notice Notifies about a moving funds wallet whose BTC balance is\\n /// below the moving funds dust threshold. Ends the moving funds\\n /// process and begins wallet closing immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known\\n /// on the Ethereum chain.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain.\\n /// If the wallet has no main UTXO, this parameter can be empty as it\\n /// is ignored,\\n /// - The wallet BTC balance must be below the moving funds threshold.\\n function notifyMovingFundsBelowDust(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external {\\n self.notifyMovingFundsBelowDust(walletPubKeyHash, mainUtxo);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moved funds sweep\\n /// transaction and to make the necessary state changes. Moved\\n /// funds sweep is only accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the sweep transaction structure by\\n /// checking if it actually spends the moved funds UTXO and the\\n /// sweeping wallet's main UTXO (optionally), and if it locks the\\n /// value on the sweeping wallet's 20-byte public key hash using a\\n /// reasonable transaction fee. If all preconditions are\\n /// met, this function updates the sweeping wallet main UTXO, thus\\n /// their BTC balance.\\n ///\\n /// It is possible to prove the given sweep transaction only\\n /// one time.\\n /// @param sweepTx Bitcoin sweep funds transaction data.\\n /// @param sweepProof Bitcoin sweep funds proof data.\\n /// @param mainUtxo Data of the sweeping wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\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\\n /// the first input pointing to a moved funds sweep request targeted\\n /// to the wallet, and optionally, the second input pointing to the\\n /// wallet's main UTXO, if the sweeping wallet has a main UTXO set.\\n /// There should be only one output locking funds on the sweeping\\n /// wallet 20-byte public key hash,\\n /// - `sweepProof` components must match the expected structure.\\n /// See `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 sweeping wallet, as currently known on the Ethereum chain.\\n /// If there is no main UTXO, this parameter is ignored,\\n /// - The sweeping wallet must be in the Live or MovingFunds state,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movedFundsSweepTxMaxTotalFee` governable parameter.\\n function submitMovedFundsSweepProof(\\n BitcoinTx.Info calldata sweepTx,\\n BitcoinTx.Proof calldata sweepProof,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external onlySpvMaintainer {\\n self.submitMovedFundsSweepProof(sweepTx, sweepProof, mainUtxo);\\n }\\n\\n /// @notice Notifies about a timed out moved funds sweep process. If the\\n /// wallet is not terminated yet, that function terminates\\n /// the wallet and slashes signing group members as a result.\\n /// Marks the given sweep request as TimedOut.\\n /// @param movingFundsTxHash 32-byte hash of the moving funds transaction\\n /// that caused the sweep request to be created.\\n /// @param movingFundsTxOutputIndex Index of the moving funds transaction\\n /// output that is subject of the sweep request.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The moved funds sweep request must be in the Pending state,\\n /// - The moved funds sweep timeout must be actually exceeded,\\n /// - The wallet must be either in the Live or MovingFunds or\\n /// Terminated state,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovedFundsSweepTimeout(\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n self.notifyMovedFundsSweepTimeout(\\n movingFundsTxHash,\\n movingFundsTxOutputIndex,\\n walletMembersIDs\\n );\\n }\\n\\n /// @notice Requests creation of a new wallet. This function just\\n /// forms a request and the creation process is performed\\n /// asynchronously. Once a wallet is created, the ECDSA Wallet\\n /// Registry will notify this contract by calling the\\n /// `__ecdsaWalletCreatedCallback` function.\\n /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - `activeWalletMainUtxo` components must point to the recent main\\n /// UTXO of the given active wallet, as currently known on the\\n /// Ethereum chain. If there is no active wallet at the moment, or\\n /// the active wallet has no main UTXO, this parameter can be\\n /// empty as it is ignored,\\n /// - Wallet creation must not be in progress,\\n /// - If the active wallet is set, one of the following\\n /// conditions must be true:\\n /// - The active wallet BTC balance is above the minimum threshold\\n /// and the active wallet is old enough, i.e. the creation period\\n /// was elapsed since its creation time,\\n /// - The active wallet BTC balance is above the maximum threshold.\\n function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)\\n external\\n {\\n self.requestNewWallet(activeWalletMainUtxo);\\n }\\n\\n /// @notice A callback function that is called by the ECDSA Wallet Registry\\n /// once a new ECDSA wallet is created.\\n /// @param ecdsaWalletID Wallet's unique identifier.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Given wallet data must not belong to an already registered wallet.\\n function __ecdsaWalletCreatedCallback(\\n bytes32 ecdsaWalletID,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external override {\\n self.registerNewWallet(ecdsaWalletID, publicKeyX, publicKeyY);\\n }\\n\\n /// @notice A callback function that is called by the ECDSA Wallet Registry\\n /// once a wallet heartbeat failure is detected.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Wallet must be in Live state.\\n function __ecdsaWalletHeartbeatFailedCallback(\\n bytes32,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external override {\\n self.notifyWalletHeartbeatFailed(publicKeyX, publicKeyY);\\n }\\n\\n /// @notice Notifies that the wallet is either old enough or has too few\\n /// satoshi left and qualifies to be closed.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - Wallet must not be set as the current active wallet,\\n /// - Wallet must exceed the wallet maximum age OR the wallet BTC\\n /// balance must be lesser than the minimum threshold. If the latter\\n /// case is true, the `walletMainUtxo` components must point to the\\n /// recent main UTXO of the given wallet, as currently known on the\\n /// Ethereum chain. If the wallet has no main UTXO, this parameter\\n /// can be empty as it is ignored since the wallet balance is\\n /// assumed to be zero,\\n /// - Wallet must be in Live state.\\n function notifyWalletCloseable(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo\\n ) external {\\n self.notifyWalletCloseable(walletPubKeyHash, walletMainUtxo);\\n }\\n\\n /// @notice Notifies about the end of the closing period for the given wallet.\\n /// Closes the wallet ultimately and notifies the ECDSA registry\\n /// about this fact.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the Closing state,\\n /// - The wallet closing period must have elapsed.\\n function notifyWalletClosingPeriodElapsed(bytes20 walletPubKeyHash)\\n external\\n {\\n self.notifyWalletClosingPeriodElapsed(walletPubKeyHash);\\n }\\n\\n /// @notice Submits a fraud challenge indicating that a UTXO being under\\n /// wallet control was unlocked by the wallet but was not used\\n /// according to the protocol rules. That means the wallet signed\\n /// a transaction input pointing to that UTXO and there is a unique\\n /// sighash and signature pair associated with that input. This\\n /// function uses those parameters to create a fraud accusation that\\n /// proves a given transaction input unlocking the given UTXO was\\n /// actually signed by the wallet. This function cannot determine\\n /// whether the transaction was actually broadcast and the input was\\n /// consumed in a fraudulent way so it just opens a challenge period\\n /// during which the wallet can defeat the challenge by submitting\\n /// proof of a transaction that consumes the given input according\\n /// to protocol rules. To prevent spurious allegations, the caller\\n /// must deposit ETH that is returned back upon justified fraud\\n /// challenge or confiscated otherwise.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @param signature Bitcoin signature in the R/S/V format.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPublicKey` must be in Live or MovingFunds\\n /// or Closing state,\\n /// - The challenger must send appropriate amount of ETH used as\\n /// fraud challenge deposit,\\n /// - The signature (represented by r, s and v) must be generated by\\n /// the wallet behind `walletPubKey` during signing of `sighash`\\n /// which was calculated from `preimageSha256`,\\n /// - Wallet can be challenged for the given signature only once.\\n function submitFraudChallenge(\\n bytes calldata walletPublicKey,\\n bytes memory preimageSha256,\\n BitcoinTx.RSVSignature calldata signature\\n ) external payable {\\n self.submitFraudChallenge(walletPublicKey, preimageSha256, signature);\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet if\\n /// the transaction that spends the UTXO follows the protocol rules.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during input signing.\\n /// The fraud challenge defeat attempt will only succeed if the\\n /// inputs in the preimage are considered honestly spent by the\\n /// wallet. Therefore the transaction spending the UTXO must be\\n /// proven in the Bridge before a challenge defeat is called.\\n /// If successfully defeated, the fraud challenge is marked as\\n /// resolved and the amount of ether deposited by the challenger is\\n /// sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference.\\n /// @param witness Flag indicating whether the preimage was produced for a\\n /// witness input. True for witness, false for non-witness input.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`\\n /// must identify an open fraud challenge,\\n /// - the preimage must be a valid preimage of a transaction generated\\n /// according to the protocol rules and already proved in the Bridge,\\n /// - before a defeat attempt is made the transaction that spends the\\n /// given UTXO must be proven in the Bridge.\\n function defeatFraudChallenge(\\n bytes calldata walletPublicKey,\\n bytes calldata preimage,\\n bool witness\\n ) external {\\n self.defeatFraudChallenge(walletPublicKey, preimage, witness);\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet by\\n /// proving the sighash and signature were produced for an off-chain\\n /// wallet heartbeat message following a strict format.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during heartbeat message\\n /// signing. The fraud challenge defeat attempt will only succeed if\\n /// the signed message follows a strict format required for\\n /// heartbeat messages. If successfully defeated, the fraud\\n /// challenge is marked as resolved and the amount of ether\\n /// deposited by the challenger is sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param heartbeatMessage Off-chain heartbeat message meeting the heartbeat\\n /// message format requirements which produces sighash used to\\n /// generate the ECDSA signature that is the subject of the fraud\\n /// claim.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as\\n /// `hash256(heartbeatMessage)` must identify an open fraud challenge,\\n /// - `heartbeatMessage` must follow a strict format of heartbeat\\n /// messages.\\n function defeatFraudChallengeWithHeartbeat(\\n bytes calldata walletPublicKey,\\n bytes calldata heartbeatMessage\\n ) external {\\n self.defeatFraudChallengeWithHeartbeat(\\n walletPublicKey,\\n heartbeatMessage\\n );\\n }\\n\\n /// @notice Notifies about defeat timeout for the given fraud challenge.\\n /// Can be called only if there was a fraud challenge identified by\\n /// the provided `walletPublicKey` and `sighash` and it was not\\n /// defeated on time. The amount of time that needs to pass after\\n /// a fraud challenge is reported is indicated by the\\n /// `challengeDefeatTimeout`. After a successful fraud challenge\\n /// defeat timeout notification the fraud challenge is marked as\\n /// resolved, the stake of each operator is slashed, the ether\\n /// deposited is returned to the challenger and the challenger is\\n /// rewarded.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Closing or\\n /// Terminated state,\\n /// - The `walletPublicKey` and `sighash` calculated from\\n /// `preimageSha256` must identify an open fraud challenge,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time indicated by `challengeDefeatTimeout` must pass\\n /// after the challenge was reported.\\n function notifyFraudChallengeDefeatTimeout(\\n bytes calldata walletPublicKey,\\n uint32[] calldata walletMembersIDs,\\n bytes memory preimageSha256\\n ) external {\\n self.notifyFraudChallengeDefeatTimeout(\\n walletPublicKey,\\n walletMembersIDs,\\n preimageSha256\\n );\\n }\\n\\n /// @notice Allows the Governance to mark the given vault address as trusted\\n /// or no longer trusted. Vaults are not trusted by default.\\n /// Trusted vault must meet the following criteria:\\n /// - `IVault.receiveBalanceIncrease` must have a known, low gas\\n /// cost,\\n /// - `IVault.receiveBalanceIncrease` must never revert.\\n /// @dev Without restricting reveal only to trusted vaults, malicious\\n /// vaults not meeting the criteria would be able to nuke sweep proof\\n /// transactions executed by ECDSA wallet with deposits routed to\\n /// them.\\n /// @param vault The address of the vault.\\n /// @param isTrusted flag indicating whether the vault is trusted or not.\\n /// @dev Can only be called by the Governance.\\n function setVaultStatus(address vault, bool isTrusted)\\n external\\n onlyGovernance\\n {\\n self.isVaultTrusted[vault] = isTrusted;\\n emit VaultStatusUpdated(vault, isTrusted);\\n }\\n\\n /// @notice Allows the Governance to mark the given address as trusted\\n /// or no longer trusted SPV maintainer. Addresses are not trusted\\n /// as SPV maintainers by default.\\n /// @dev The SPV proof does not check whether the transaction is a part of\\n /// the Bitcoin mainnet, it only checks whether the transaction has been\\n /// mined performing the required amount of work as on Bitcoin mainnet.\\n /// The possibility of submitting SPV proofs is limited to trusted SPV\\n /// maintainers. The system expects transaction confirmations with the\\n /// required work accumulated, so trusted SPV maintainers can not prove\\n /// the transaction without providing the required Bitcoin proof of work.\\n /// Trusted maintainers address the issue of an economic game between\\n /// tBTC and Bitcoin mainnet where large Bitcoin mining pools can decide\\n /// to use their hash power to mine fake Bitcoin blocks to prove them in\\n /// tBTC instead of receiving Bitcoin miner rewards.\\n /// @param spvMaintainer The address of the SPV maintainer.\\n /// @param isTrusted flag indicating whether the address is trusted or not.\\n /// @dev Can only be called by the Governance.\\n function setSpvMaintainerStatus(address spvMaintainer, bool isTrusted)\\n external\\n onlyGovernance\\n {\\n self.isSpvMaintainer[spvMaintainer] = isTrusted;\\n emit SpvMaintainerStatusUpdated(spvMaintainer, isTrusted);\\n }\\n\\n /// @notice Updates parameters of deposits.\\n /// @param depositDustThreshold New value of the deposit dust threshold in\\n /// satoshis. It is the minimal amount that can be requested to\\n //// deposit. Value of this parameter must take into account the value\\n /// of `depositTreasuryFeeDivisor` and `depositTxMaxFee` parameters\\n /// in order to make requests that can incur the treasury and\\n /// transaction fee and still satisfy the depositor.\\n /// @param depositTreasuryFeeDivisor New value of the treasury fee divisor.\\n /// It is the divisor used to compute the treasury fee taken from\\n /// each deposit and transferred to the treasury upon sweep proof\\n /// submission. That fee is computed as follows:\\n /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each deposit,\\n /// the `depositTreasuryFeeDivisor` should be set to `50`\\n /// because `1/50 = 0.02 = 2%`.\\n /// @param depositTxMaxFee New value of the deposit tx max fee in satoshis.\\n /// It is the maximum amount of BTC transaction fee that can\\n /// be incurred by each swept deposit being part of the given sweep\\n /// transaction. If the maximum BTC transaction fee is exceeded,\\n /// such transaction is considered a fraud.\\n /// @param depositRevealAheadPeriod New value of the deposit reveal ahead\\n /// period parameter in seconds. It defines the length of the period\\n /// that must be preserved between the deposit reveal time and the\\n /// deposit refund locktime.\\n /// @dev Requirements:\\n /// - Deposit dust threshold must be greater than zero,\\n /// - Deposit dust threshold must be greater than deposit TX max fee,\\n /// - Deposit transaction max fee must be greater than zero.\\n function updateDepositParameters(\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n ) external onlyGovernance {\\n self.updateDepositParameters(\\n depositDustThreshold,\\n depositTreasuryFeeDivisor,\\n depositTxMaxFee,\\n depositRevealAheadPeriod\\n );\\n }\\n\\n /// @notice Updates parameters of redemptions.\\n /// @param redemptionDustThreshold New value of the redemption dust\\n /// threshold in satoshis. It is the minimal amount that can be\\n /// requested for redemption. Value of this parameter must take into\\n /// account the value of `redemptionTreasuryFeeDivisor` and\\n /// `redemptionTxMaxFee` parameters in order to make requests that\\n /// can incur the treasury and transaction fee and still satisfy the\\n /// redeemer.\\n /// @param redemptionTreasuryFeeDivisor New value of the redemption\\n /// treasury fee divisor. It is the divisor used to compute the\\n /// treasury fee taken from each redemption request and transferred\\n /// to the treasury upon successful request finalization. That fee is\\n /// computed as follows:\\n /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each\\n /// redemption request, the `redemptionTreasuryFeeDivisor` should\\n /// be set to `50` because `1/50 = 0.02 = 2%`.\\n /// @param redemptionTxMaxFee New value of the redemption transaction max\\n /// fee in satoshis. It is the maximum amount of BTC transaction fee\\n /// that can be incurred by each redemption request being part of the\\n /// given redemption transaction. If the maximum BTC transaction fee\\n /// is exceeded, such transaction is considered a fraud.\\n /// This is a per-redemption output max fee for the redemption\\n /// transaction.\\n /// @param redemptionTxMaxTotalFee New value of the redemption transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single redemption\\n /// transaction. This is a _total_ max fee for the entire redemption\\n /// transaction.\\n /// @param redemptionTimeout New value of the redemption timeout in seconds.\\n /// It is the time after which the redemption request can be reported\\n /// as timed out. It is counted from the moment when the redemption\\n /// request was created via `requestRedemption` call. Reported timed\\n /// out requests are cancelled and locked balance is returned to the\\n /// redeemer in full amount.\\n /// @param redemptionTimeoutSlashingAmount New value of the redemption\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for redemption timeout.\\n /// @param redemptionTimeoutNotifierRewardMultiplier New value of the\\n /// redemption timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a redemption timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Redemption dust threshold must be greater than moving funds dust\\n /// threshold,\\n /// - Redemption dust threshold must be greater than the redemption TX\\n /// max fee,\\n /// - Redemption transaction max fee must be greater than zero,\\n /// - Redemption transaction max total fee must be greater than or\\n /// equal to the redemption transaction per-request max fee,\\n /// - Redemption timeout must be greater than zero,\\n /// - Redemption timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateRedemptionParameters(\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n ) external onlyGovernance {\\n self.updateRedemptionParameters(\\n redemptionDustThreshold,\\n redemptionTreasuryFeeDivisor,\\n redemptionTxMaxFee,\\n redemptionTxMaxTotalFee,\\n redemptionTimeout,\\n redemptionTimeoutSlashingAmount,\\n redemptionTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of moving funds.\\n /// @param movingFundsTxMaxTotalFee New value of the moving funds transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single moving funds\\n /// transaction. This is a _total_ max fee for the entire moving\\n /// funds transaction.\\n /// @param movingFundsDustThreshold New value of the moving funds dust\\n /// threshold. It is the minimal satoshi amount that makes sense to\\n /// be transferred during the moving funds process. Moving funds\\n /// wallets having their BTC balance below that value can begin\\n /// closing immediately as transferring such a low value may not be\\n /// possible due to BTC network fees.\\n /// @param movingFundsTimeoutResetDelay New value of the moving funds\\n /// timeout reset delay in seconds. It is the time after which the\\n /// moving funds timeout can be reset in case the target wallet\\n /// commitment cannot be submitted due to a lack of live wallets\\n /// in the system. It is counted from the moment when the wallet\\n /// was requested to move their funds and switched to the MovingFunds\\n /// state or from the moment the timeout was reset the last time.\\n /// @param movingFundsTimeout New value of the moving funds timeout in\\n /// seconds. It is the time after which the moving funds process can\\n /// be reported as timed out. It is counted from the moment when the\\n /// wallet was requested to move their funds and switched to the\\n /// MovingFunds state.\\n /// @param movingFundsTimeoutSlashingAmount New value of the moving funds\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for moving funds timeout.\\n /// @param movingFundsTimeoutNotifierRewardMultiplier New value of the\\n /// moving funds timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a moving funds timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @param movingFundsCommitmentGasOffset New value of the gas offset for\\n /// moving funds target wallet commitment transaction gas costs\\n /// reimbursement.\\n /// @param movedFundsSweepTxMaxTotalFee New value of the moved funds sweep\\n /// transaction max total fee in satoshis. It is the maximum amount\\n /// of the total BTC transaction fee that is acceptable in a single\\n /// moved funds sweep transaction. This is a _total_ max fee for the\\n /// entire moved funds sweep transaction.\\n /// @param movedFundsSweepTimeout New value of the moved funds sweep\\n /// timeout in seconds. It is the time after which the moved funds\\n /// sweep process can be reported as timed out. It is counted from\\n /// the moment when the wallet was requested to sweep the received\\n /// funds.\\n /// @param movedFundsSweepTimeoutSlashingAmount New value of the moved\\n /// funds sweep timeout slashing amount in T, it is the amount\\n /// slashed from each wallet member for moved funds sweep timeout.\\n /// @param movedFundsSweepTimeoutNotifierRewardMultiplier New value of\\n /// the moved funds sweep timeout notifier reward multiplier as\\n /// percentage, it determines the percentage of the notifier reward\\n /// from the staking contact the notifier of a moved funds sweep\\n /// timeout receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Moving funds transaction max total fee must be greater than zero,\\n /// - Moving funds dust threshold must be greater than zero and lower\\n /// than the redemption dust threshold,\\n /// - Moving funds timeout reset delay must be greater than zero,\\n /// - Moving funds timeout must be greater than the moving funds\\n /// timeout reset delay,\\n /// - Moving funds timeout notifier reward multiplier must be in the\\n /// range [0, 100],\\n /// - Moved funds sweep transaction max total fee must be greater than zero,\\n /// - Moved funds sweep timeout must be greater than zero,\\n /// - Moved funds sweep timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateMovingFundsParameters(\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n ) external onlyGovernance {\\n self.updateMovingFundsParameters(\\n movingFundsTxMaxTotalFee,\\n movingFundsDustThreshold,\\n movingFundsTimeoutResetDelay,\\n movingFundsTimeout,\\n movingFundsTimeoutSlashingAmount,\\n movingFundsTimeoutNotifierRewardMultiplier,\\n movingFundsCommitmentGasOffset,\\n movedFundsSweepTxMaxTotalFee,\\n movedFundsSweepTimeout,\\n movedFundsSweepTimeoutSlashingAmount,\\n movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of wallets.\\n /// @param walletCreationPeriod New value of the wallet creation period in\\n /// seconds, determines how frequently a new wallet creation can be\\n /// requested.\\n /// @param walletCreationMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param walletCreationMaxBtcBalance New value of the wallet maximum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param walletClosureMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet closure.\\n /// @param walletMaxAge New value of the wallet maximum age in seconds,\\n /// indicates the maximum age of a wallet in seconds, after which\\n /// the wallet moving funds process can be requested.\\n /// @param walletMaxBtcTransfer New value of the wallet maximum BTC transfer\\n /// in satoshi, determines the maximum amount that can be transferred\\n // to a single target wallet during the moving funds process.\\n /// @param walletClosingPeriod New value of the wallet closing period in\\n /// seconds, determines the length of the wallet closing period,\\n // i.e. the period when the wallet remains in the Closing state\\n // and can be subject of deposit fraud challenges.\\n /// @dev Requirements:\\n /// - Wallet maximum BTC balance must be greater than the wallet\\n /// minimum BTC balance,\\n /// - Wallet maximum BTC transfer must be greater than zero,\\n /// - Wallet closing period must be greater than zero.\\n function updateWalletParameters(\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n ) external onlyGovernance {\\n self.updateWalletParameters(\\n walletCreationPeriod,\\n walletCreationMinBtcBalance,\\n walletCreationMaxBtcBalance,\\n walletClosureMinBtcBalance,\\n walletMaxAge,\\n walletMaxBtcTransfer,\\n walletClosingPeriod\\n );\\n }\\n\\n /// @notice Updates parameters related to frauds.\\n /// @param fraudChallengeDepositAmount New value of the fraud challenge\\n /// deposit amount in wei, it is the amount of ETH the party\\n /// challenging the wallet for fraud needs to deposit.\\n /// @param fraudChallengeDefeatTimeout New value of the challenge defeat\\n /// timeout in seconds, it is the amount of time the wallet has to\\n /// defeat a fraud challenge. The value must be greater than zero.\\n /// @param fraudSlashingAmount New value of the fraud slashing amount in T,\\n /// it is the amount slashed from each wallet member for committing\\n /// a fraud.\\n /// @param fraudNotifierRewardMultiplier New value of the fraud notifier\\n /// reward multiplier as percentage, it determines the percentage of\\n /// the notifier reward from the staking contact the notifier of\\n /// a fraud receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Fraud challenge defeat timeout must be greater than 0,\\n /// - Fraud notifier reward multiplier must be in the range [0, 100].\\n function updateFraudParameters(\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n ) external onlyGovernance {\\n self.updateFraudParameters(\\n fraudChallengeDepositAmount,\\n fraudChallengeDefeatTimeout,\\n fraudSlashingAmount,\\n fraudNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates treasury address. The treasury receives the system fees.\\n /// @param treasury New value of the treasury address.\\n /// @dev The treasury address must not be 0x0.\\n // slither-disable-next-line shadowing-local\\n function updateTreasury(address treasury) external onlyGovernance {\\n self.updateTreasury(treasury);\\n }\\n\\n /// @notice Collection of all revealed deposits indexed by\\n /// keccak256(fundingTxHash | fundingOutputIndex).\\n /// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)\\n /// and fundingOutputIndex an uint32. This mapping may contain valid\\n /// and invalid deposits and the wallet is responsible for\\n /// validating them before attempting to execute a sweep.\\n function deposits(uint256 depositKey)\\n external\\n view\\n returns (Deposit.DepositRequest memory)\\n {\\n return self.deposits[depositKey];\\n }\\n\\n /// @notice Collection of all pending redemption requests indexed by\\n /// redemption key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The walletPubKeyHash is the 20-byte wallet's public key hash\\n /// (computed using Bitcoin HASH160 over the compressed ECDSA\\n /// public key) and `redeemerOutputScript` is a Bitcoin script\\n /// (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC as requested by the redeemer. Requests are added\\n /// to this mapping by the `requestRedemption` method (duplicates\\n /// not allowed) and are removed by one of the following methods:\\n /// - `submitRedemptionProof` in case the request was handled\\n /// successfully,\\n /// - `notifyRedemptionTimeout` in case the request was reported\\n /// to be timed out.\\n function pendingRedemptions(uint256 redemptionKey)\\n external\\n view\\n returns (Redemption.RedemptionRequest memory)\\n {\\n return self.pendingRedemptions[redemptionKey];\\n }\\n\\n /// @notice Collection of all timed out redemptions requests indexed by\\n /// redemption key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The walletPubKeyHash is the 20-byte wallet's public key hash\\n /// (computed using Bitcoin HASH160 over the compressed ECDSA\\n /// public key) and `redeemerOutputScript` is the Bitcoin script\\n /// (P2PKH, P2WPKH, P2SH or P2WSH) that is involved in the timed\\n /// out request.\\n /// Only one method can add to this mapping:\\n /// - `notifyRedemptionTimeout` which puts the redemption key\\n /// to this mapping based on a timed out request stored\\n /// previously in `pendingRedemptions` mapping.\\n /// Only one method can remove entries from this mapping:\\n /// - `submitRedemptionProof` in case the timed out redemption\\n /// request was a part of the proven transaction.\\n function timedOutRedemptions(uint256 redemptionKey)\\n external\\n view\\n returns (Redemption.RedemptionRequest memory)\\n {\\n return self.timedOutRedemptions[redemptionKey];\\n }\\n\\n /// @notice Collection of main UTXOs that are honestly spent indexed by\\n /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash\\n /// is bytes32 (ordered as in Bitcoin internally) and\\n /// fundingOutputIndex an uint32. A main UTXO is considered honestly\\n /// spent if it was used as an input of a transaction that have been\\n /// proven in the Bridge.\\n function spentMainUTXOs(uint256 utxoKey) external view returns (bool) {\\n return self.spentMainUTXOs[utxoKey];\\n }\\n\\n /// @notice Gets details about a registered wallet.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @return Wallet details.\\n function wallets(bytes20 walletPubKeyHash)\\n external\\n view\\n returns (Wallets.Wallet memory)\\n {\\n return self.registeredWallets[walletPubKeyHash];\\n }\\n\\n /// @notice Gets the public key hash of the active wallet.\\n /// @return The 20-byte public key hash (computed using Bitcoin HASH160\\n /// over the compressed ECDSA public key) of the active wallet.\\n /// Returns bytes20(0) if there is no active wallet at the moment.\\n function activeWalletPubKeyHash() external view returns (bytes20) {\\n return self.activeWalletPubKeyHash;\\n }\\n\\n /// @notice Gets the live wallets count.\\n /// @return The current count of wallets being in the Live state.\\n function liveWalletsCount() external view returns (uint32) {\\n return self.liveWalletsCount;\\n }\\n\\n /// @notice Returns the fraud challenge identified by the given key built\\n /// as keccak256(walletPublicKey|sighash).\\n function fraudChallenges(uint256 challengeKey)\\n external\\n view\\n returns (Fraud.FraudChallenge memory)\\n {\\n return self.fraudChallenges[challengeKey];\\n }\\n\\n /// @notice Collection of all moved funds sweep requests indexed by\\n /// `keccak256(movingFundsTxHash | movingFundsOutputIndex)`.\\n /// The `movingFundsTxHash` is `bytes32` (ordered as in Bitcoin\\n /// internally) and `movingFundsOutputIndex` an `uint32`. Each entry\\n /// is actually an UTXO representing the moved funds and is supposed\\n /// to be swept with the current main UTXO of the recipient wallet.\\n /// @param requestKey Request key built as\\n /// `keccak256(movingFundsTxHash | movingFundsOutputIndex)`.\\n /// @return Details of the moved funds sweep request.\\n function movedFundsSweepRequests(uint256 requestKey)\\n external\\n view\\n returns (MovingFunds.MovedFundsSweepRequest memory)\\n {\\n return self.movedFundsSweepRequests[requestKey];\\n }\\n\\n /// @notice Indicates if the vault with the given address is trusted or not.\\n /// Depositors can route their revealed deposits only to trusted\\n /// vaults and have trusted vaults notified about new deposits as\\n /// soon as these deposits get swept. Vaults not trusted by the\\n /// Bridge can still be used by Bank balance owners on their own\\n /// responsibility - anyone can approve their Bank balance to any\\n /// address.\\n function isVaultTrusted(address vault) external view returns (bool) {\\n return self.isVaultTrusted[vault];\\n }\\n\\n /// @notice Returns the current values of Bridge deposit parameters.\\n /// @return depositDustThreshold The minimal amount that can be requested\\n /// to deposit. Value of this parameter must take into account the\\n /// value of `depositTreasuryFeeDivisor` and `depositTxMaxFee`\\n /// parameters in order to make requests that can incur the\\n /// treasury and transaction fee and still satisfy the depositor.\\n /// @return depositTreasuryFeeDivisor Divisor used to compute the treasury\\n /// fee taken from each deposit and transferred to the treasury upon\\n /// sweep proof submission. That fee is computed as follows:\\n /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each deposit,\\n /// the `depositTreasuryFeeDivisor` should be set to `50`\\n /// because `1/50 = 0.02 = 2%`.\\n /// @return depositTxMaxFee Maximum amount of BTC transaction fee that can\\n /// be incurred by each swept deposit being part of the given sweep\\n /// transaction. If the maximum BTC transaction fee is exceeded,\\n /// such transaction is considered a fraud.\\n /// @return depositRevealAheadPeriod Defines the length of the period that\\n /// must be preserved between the deposit reveal time and the\\n /// deposit refund locktime. For example, if the deposit become\\n /// refundable on August 1st, and the ahead period is 7 days, the\\n /// latest moment for deposit reveal is July 25th. Value in seconds.\\n function depositParameters()\\n external\\n view\\n returns (\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n )\\n {\\n depositDustThreshold = self.depositDustThreshold;\\n depositTreasuryFeeDivisor = self.depositTreasuryFeeDivisor;\\n depositTxMaxFee = self.depositTxMaxFee;\\n depositRevealAheadPeriod = self.depositRevealAheadPeriod;\\n }\\n\\n /// @notice Returns the current values of Bridge redemption parameters.\\n /// @return redemptionDustThreshold The minimal amount that can be requested\\n /// for redemption. Value of this parameter must take into account\\n /// the value of `redemptionTreasuryFeeDivisor` and `redemptionTxMaxFee`\\n /// parameters in order to make requests that can incur the\\n /// treasury and transaction fee and still satisfy the redeemer.\\n /// @return redemptionTreasuryFeeDivisor Divisor used to compute the treasury\\n /// fee taken from each redemption request and transferred to the\\n /// treasury upon successful request finalization. That fee is\\n /// computed as follows:\\n /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each\\n /// redemption request, the `redemptionTreasuryFeeDivisor` should\\n /// be set to `50` because `1/50 = 0.02 = 2%`.\\n /// @return redemptionTxMaxFee Maximum amount of BTC transaction fee that\\n /// can be incurred by each redemption request being part of the\\n /// given redemption transaction. If the maximum BTC transaction\\n /// fee is exceeded, such transaction is considered a fraud.\\n /// This is a per-redemption output max fee for the redemption\\n /// transaction.\\n /// @return redemptionTxMaxTotalFee Maximum amount of the total BTC\\n /// transaction fee that is acceptable in a single redemption\\n /// transaction. This is a _total_ max fee for the entire redemption\\n /// transaction.\\n /// @return redemptionTimeout Time after which the redemption request can be\\n /// reported as timed out. It is counted from the moment when the\\n /// redemption request was created via `requestRedemption` call.\\n /// Reported timed out requests are cancelled and locked balance is\\n /// returned to the redeemer in full amount.\\n /// @return redemptionTimeoutSlashingAmount The amount of stake slashed\\n /// from each member of a wallet for a redemption timeout.\\n /// @return redemptionTimeoutNotifierRewardMultiplier The percentage of the\\n /// notifier reward from the staking contract the notifier of a\\n /// redemption timeout receives. The value is in the range [0, 100].\\n function redemptionParameters()\\n external\\n view\\n returns (\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n )\\n {\\n redemptionDustThreshold = self.redemptionDustThreshold;\\n redemptionTreasuryFeeDivisor = self.redemptionTreasuryFeeDivisor;\\n redemptionTxMaxFee = self.redemptionTxMaxFee;\\n redemptionTxMaxTotalFee = self.redemptionTxMaxTotalFee;\\n redemptionTimeout = self.redemptionTimeout;\\n redemptionTimeoutSlashingAmount = self.redemptionTimeoutSlashingAmount;\\n redemptionTimeoutNotifierRewardMultiplier = self\\n .redemptionTimeoutNotifierRewardMultiplier;\\n }\\n\\n /// @notice Returns the current values of Bridge moving funds between\\n /// wallets parameters.\\n /// @return movingFundsTxMaxTotalFee Maximum amount of the total BTC\\n /// transaction fee that is acceptable in a single moving funds\\n /// transaction. This is a _total_ max fee for the entire moving\\n /// funds transaction.\\n /// @return movingFundsDustThreshold The minimal satoshi amount that makes\\n /// sense to be transferred during the moving funds process. Moving\\n /// funds wallets having their BTC balance below that value can\\n /// begin closing immediately as transferring such a low value may\\n /// not be possible due to BTC network fees.\\n /// @return movingFundsTimeoutResetDelay Time after which the moving funds\\n /// timeout can be reset in case the target wallet commitment\\n /// cannot be submitted due to a lack of live wallets in the system.\\n /// It is counted from the moment when the wallet was requested to\\n /// move their funds and switched to the MovingFunds state or from\\n /// the moment the timeout was reset the last time. Value in seconds\\n /// This value should be lower than the value of the\\n /// `movingFundsTimeout`.\\n /// @return movingFundsTimeout Time after which the moving funds process\\n /// can be reported as timed out. It is counted from the moment\\n /// when the wallet was requested to move their funds and switched\\n /// to the MovingFunds state. Value in seconds.\\n /// @return movingFundsTimeoutSlashingAmount The amount of stake slashed\\n /// from each member of a wallet for a moving funds timeout.\\n /// @return movingFundsTimeoutNotifierRewardMultiplier The percentage of the\\n /// notifier reward from the staking contract the notifier of a\\n /// moving funds timeout receives. The value is in the range [0, 100].\\n /// @return movingFundsCommitmentGasOffset The gas offset used for the\\n /// moving funds target wallet commitment transaction cost\\n /// reimbursement.\\n /// @return movedFundsSweepTxMaxTotalFee Maximum amount of the total BTC\\n /// transaction fee that is acceptable in a single moved funds\\n /// sweep transaction. This is a _total_ max fee for the entire\\n /// moved funds sweep transaction.\\n /// @return movedFundsSweepTimeout Time after which the moved funds sweep\\n /// process can be reported as timed out. It is counted from the\\n /// moment when the wallet was requested to sweep the received funds.\\n /// Value in seconds.\\n /// @return movedFundsSweepTimeoutSlashingAmount The amount of stake slashed\\n /// from each member of a wallet for a moved funds sweep timeout.\\n /// @return movedFundsSweepTimeoutNotifierRewardMultiplier The percentage\\n /// of the notifier reward from the staking contract the notifier\\n /// of a moved funds sweep timeout receives. The value is in the\\n /// range [0, 100].\\n function movingFundsParameters()\\n external\\n view\\n returns (\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n )\\n {\\n movingFundsTxMaxTotalFee = self.movingFundsTxMaxTotalFee;\\n movingFundsDustThreshold = self.movingFundsDustThreshold;\\n movingFundsTimeoutResetDelay = self.movingFundsTimeoutResetDelay;\\n movingFundsTimeout = self.movingFundsTimeout;\\n movingFundsTimeoutSlashingAmount = self\\n .movingFundsTimeoutSlashingAmount;\\n movingFundsTimeoutNotifierRewardMultiplier = self\\n .movingFundsTimeoutNotifierRewardMultiplier;\\n movingFundsCommitmentGasOffset = self.movingFundsCommitmentGasOffset;\\n movedFundsSweepTxMaxTotalFee = self.movedFundsSweepTxMaxTotalFee;\\n movedFundsSweepTimeout = self.movedFundsSweepTimeout;\\n movedFundsSweepTimeoutSlashingAmount = self\\n .movedFundsSweepTimeoutSlashingAmount;\\n movedFundsSweepTimeoutNotifierRewardMultiplier = self\\n .movedFundsSweepTimeoutNotifierRewardMultiplier;\\n }\\n\\n /// @return walletCreationPeriod Determines how frequently a new wallet\\n /// creation can be requested. Value in seconds.\\n /// @return walletCreationMinBtcBalance The minimum BTC threshold in satoshi\\n /// that is used to decide about wallet creation.\\n /// @return walletCreationMaxBtcBalance The maximum BTC threshold in satoshi\\n /// that is used to decide about wallet creation.\\n /// @return walletClosureMinBtcBalance The minimum BTC threshold in satoshi\\n /// that is used to decide about wallet closure.\\n /// @return walletMaxAge The maximum age of a wallet in seconds, after which\\n /// the wallet moving funds process can be requested.\\n /// @return walletMaxBtcTransfer The maximum BTC amount in satoshi than\\n /// can be transferred to a single target wallet during the moving\\n /// funds process.\\n /// @return walletClosingPeriod Determines the length of the wallet closing\\n /// period, i.e. the period when the wallet remains in the Closing\\n /// state and can be subject of deposit fraud challenges. Value\\n /// in seconds.\\n function walletParameters()\\n external\\n view\\n returns (\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n )\\n {\\n walletCreationPeriod = self.walletCreationPeriod;\\n walletCreationMinBtcBalance = self.walletCreationMinBtcBalance;\\n walletCreationMaxBtcBalance = self.walletCreationMaxBtcBalance;\\n walletClosureMinBtcBalance = self.walletClosureMinBtcBalance;\\n walletMaxAge = self.walletMaxAge;\\n walletMaxBtcTransfer = self.walletMaxBtcTransfer;\\n walletClosingPeriod = self.walletClosingPeriod;\\n }\\n\\n /// @notice Returns the current values of Bridge fraud parameters.\\n /// @return fraudChallengeDepositAmount The amount of ETH in wei the party\\n /// challenging the wallet for fraud needs to deposit.\\n /// @return fraudChallengeDefeatTimeout The amount of time the wallet has to\\n /// defeat a fraud challenge.\\n /// @return fraudSlashingAmount The amount slashed from each wallet member\\n /// for committing a fraud.\\n /// @return fraudNotifierRewardMultiplier The percentage of the notifier\\n /// reward from the staking contract the notifier of a fraud\\n /// receives. The value is in the range [0, 100].\\n function fraudParameters()\\n external\\n view\\n returns (\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n )\\n {\\n fraudChallengeDepositAmount = self.fraudChallengeDepositAmount;\\n fraudChallengeDefeatTimeout = self.fraudChallengeDefeatTimeout;\\n fraudSlashingAmount = self.fraudSlashingAmount;\\n fraudNotifierRewardMultiplier = self.fraudNotifierRewardMultiplier;\\n }\\n\\n /// @notice Returns the addresses of contracts Bridge is interacting with.\\n /// @return bank Address of the Bank the Bridge belongs to.\\n /// @return relay Address of the Bitcoin relay providing the current Bitcoin\\n /// network difficulty.\\n /// @return ecdsaWalletRegistry Address of the ECDSA Wallet Registry.\\n /// @return reimbursementPool Address of the Reimbursement Pool.\\n function contractReferences()\\n external\\n view\\n returns (\\n Bank bank,\\n IRelay relay,\\n EcdsaWalletRegistry ecdsaWalletRegistry,\\n ReimbursementPool reimbursementPool\\n )\\n {\\n bank = self.bank;\\n relay = self.relay;\\n ecdsaWalletRegistry = self.ecdsaWalletRegistry;\\n reimbursementPool = self.reimbursementPool;\\n }\\n\\n /// @notice Address where the deposit treasury fees will be sent to.\\n /// Treasury takes part in the operators rewarding process.\\n function treasury() external view returns (address) {\\n return self.treasury;\\n }\\n\\n /// @notice The number of confirmations on the Bitcoin chain required to\\n /// successfully evaluate an SPV proof.\\n function txProofDifficultyFactor() external view returns (uint256) {\\n return self.txProofDifficultyFactor;\\n }\\n}\\n\",\"keccak256\":\"0x0a6e8f890ba55fbd8671f4853c882e5c8c70e476d521013b21a30fa7d0f5bafd\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/BridgeState.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {IWalletRegistry as EcdsaWalletRegistry} from \\\"@keep-network/ecdsa/contracts/api/IWalletRegistry.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/ReimbursementPool.sol\\\";\\n\\nimport \\\"./IRelay.sol\\\";\\nimport \\\"./Deposit.sol\\\";\\nimport \\\"./Redemption.sol\\\";\\nimport \\\"./Fraud.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\nimport \\\"./MovingFunds.sol\\\";\\n\\nimport \\\"../bank/Bank.sol\\\";\\n\\nlibrary BridgeState {\\n struct Storage {\\n // Address of the Bank the Bridge belongs to.\\n Bank bank;\\n // Bitcoin relay providing the current Bitcoin network difficulty.\\n IRelay relay;\\n // The number of confirmations on the Bitcoin chain required to\\n // successfully evaluate an SPV proof.\\n uint96 txProofDifficultyFactor;\\n // ECDSA Wallet Registry contract handle.\\n EcdsaWalletRegistry ecdsaWalletRegistry;\\n // Reimbursement Pool contract handle.\\n ReimbursementPool reimbursementPool;\\n // Address where the deposit and redemption treasury fees will be sent\\n // to. Treasury takes part in the operators rewarding process.\\n address treasury;\\n // Move depositDustThreshold to the next storage slot for a more\\n // efficient variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __treasuryAlignmentGap;\\n // The minimal amount that can be requested to deposit.\\n // Value of this parameter must take into account the value of\\n // `depositTreasuryFeeDivisor` and `depositTxMaxFee` parameters in order\\n // to make requests that can incur the treasury and transaction fee and\\n // still satisfy the depositor.\\n uint64 depositDustThreshold;\\n // Divisor used to compute the treasury fee taken from each deposit and\\n // transferred to the treasury upon sweep proof submission. That fee is\\n // computed as follows:\\n // `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n // For example, if the treasury fee needs to be 2% of each deposit,\\n // the `depositTreasuryFeeDivisor` should be set to `50` because\\n // `1/50 = 0.02 = 2%`.\\n uint64 depositTreasuryFeeDivisor;\\n // Maximum amount of BTC transaction fee that can be incurred by each\\n // swept deposit being part of the given sweep transaction. If the\\n // maximum BTC transaction fee is exceeded, such transaction is\\n // considered a fraud.\\n //\\n // This is a per-deposit input max fee for the sweep transaction.\\n uint64 depositTxMaxFee;\\n // Defines the length of the period that must be preserved between\\n // the deposit reveal time and the deposit refund locktime. For example,\\n // if the deposit become refundable on August 1st, and the ahead period\\n // is 7 days, the latest moment for deposit reveal is July 25th.\\n // Value in seconds. The value equal to zero disables the validation\\n // of this parameter.\\n uint32 depositRevealAheadPeriod;\\n // Move movingFundsTxMaxTotalFee to the next storage slot for a more\\n // efficient variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __depositAlignmentGap;\\n // Maximum amount of the total BTC transaction fee that is acceptable in\\n // a single moving funds transaction.\\n //\\n // This is a TOTAL max fee for the moving funds transaction. Note\\n // that `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`\\n // is per single redemption. `movingFundsTxMaxTotalFee` is a total\\n // fee for the entire transaction.\\n uint64 movingFundsTxMaxTotalFee;\\n // The minimal satoshi amount that makes sense to be transferred during\\n // the moving funds process. Moving funds wallets having their BTC\\n // balance below that value can begin closing immediately as\\n // transferring such a low value may not be possible due to\\n // BTC network fees. The value of this parameter must always be lower\\n // than `redemptionDustThreshold` in order to prevent redemption requests\\n // with values lower or equal to `movingFundsDustThreshold`.\\n uint64 movingFundsDustThreshold;\\n // Time after which the moving funds timeout can be reset in case the\\n // target wallet commitment cannot be submitted due to a lack of live\\n // wallets in the system. It is counted from the moment when the wallet\\n // was requested to move their funds and switched to the MovingFunds\\n // state or from the moment the timeout was reset the last time.\\n // Value in seconds. This value should be lower than the value\\n // of the `movingFundsTimeout`.\\n uint32 movingFundsTimeoutResetDelay;\\n // Time after which the moving funds process can be reported as\\n // timed out. It is counted from the moment when the wallet\\n // was requested to move their funds and switched to the MovingFunds\\n // state. Value in seconds.\\n uint32 movingFundsTimeout;\\n // The amount of stake slashed from each member of a wallet for a moving\\n // funds timeout.\\n uint96 movingFundsTimeoutSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a moving funds timeout receives. The value is in the\\n // range [0, 100].\\n uint32 movingFundsTimeoutNotifierRewardMultiplier;\\n // The gas offset used for the target wallet commitment transaction cost\\n // reimbursement.\\n uint16 movingFundsCommitmentGasOffset;\\n // Move movedFundsSweepTxMaxTotalFee to the next storage slot for a more\\n // efficient variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __movingFundsAlignmentGap;\\n // Maximum amount of the total BTC transaction fee that is acceptable in\\n // a single moved funds sweep transaction.\\n //\\n // This is a TOTAL max fee for the moved funds sweep transaction. Note\\n // that `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`\\n // is per single redemption. `movedFundsSweepTxMaxTotalFee` is a total\\n // fee for the entire transaction.\\n uint64 movedFundsSweepTxMaxTotalFee;\\n // Time after which the moved funds sweep process can be reported as\\n // timed out. It is counted from the moment when the recipient wallet\\n // was requested to sweep the received funds. Value in seconds.\\n uint32 movedFundsSweepTimeout;\\n // The amount of stake slashed from each member of a wallet for a moved\\n // funds sweep timeout.\\n uint96 movedFundsSweepTimeoutSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a moved funds sweep timeout receives. The value is\\n // in the range [0, 100].\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier;\\n // The minimal amount that can be requested for redemption.\\n // Value of this parameter must take into account the value of\\n // `redemptionTreasuryFeeDivisor` and `redemptionTxMaxFee`\\n // parameters in order to make requests that can incur the\\n // treasury and transaction fee and still satisfy the redeemer.\\n // Additionally, the value of this parameter must always be greater\\n // than `movingFundsDustThreshold` in order to prevent redemption\\n // requests with values lower or equal to `movingFundsDustThreshold`.\\n uint64 redemptionDustThreshold;\\n // Divisor used to compute the treasury fee taken from each\\n // redemption request and transferred to the treasury upon\\n // successful request finalization. That fee is computed as follows:\\n // `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n // For example, if the treasury fee needs to be 2% of each\\n // redemption request, the `redemptionTreasuryFeeDivisor` should\\n // be set to `50` because `1/50 = 0.02 = 2%`.\\n uint64 redemptionTreasuryFeeDivisor;\\n // Maximum amount of BTC transaction fee that can be incurred by\\n // each redemption request being part of the given redemption\\n // transaction. If the maximum BTC transaction fee is exceeded, such\\n // transaction is considered a fraud.\\n //\\n // This is a per-redemption output max fee for the redemption\\n // transaction.\\n uint64 redemptionTxMaxFee;\\n // Maximum amount of the total BTC transaction fee that is acceptable in\\n // a single redemption transaction.\\n //\\n // This is a TOTAL max fee for the redemption transaction. Note\\n // that the `redemptionTxMaxFee` is per single redemption.\\n // `redemptionTxMaxTotalFee` is a total fee for the entire transaction.\\n uint64 redemptionTxMaxTotalFee;\\n // Move redemptionTimeout to the next storage slot for a more efficient\\n // variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __redemptionAlignmentGap;\\n // Time after which the redemption request can be reported as\\n // timed out. It is counted from the moment when the redemption\\n // request was created via `requestRedemption` call. Reported\\n // timed out requests are cancelled and locked TBTC is returned\\n // to the redeemer in full amount.\\n uint32 redemptionTimeout;\\n // The amount of stake slashed from each member of a wallet for a\\n // redemption timeout.\\n uint96 redemptionTimeoutSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a redemption timeout receives. The value is in the\\n // range [0, 100].\\n uint32 redemptionTimeoutNotifierRewardMultiplier;\\n // The amount of ETH in wei the party challenging the wallet for fraud\\n // needs to deposit.\\n uint96 fraudChallengeDepositAmount;\\n // The amount of time the wallet has to defeat a fraud challenge.\\n uint32 fraudChallengeDefeatTimeout;\\n // The amount of stake slashed from each member of a wallet for a fraud.\\n uint96 fraudSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a fraud receives. The value is in the range [0, 100].\\n uint32 fraudNotifierRewardMultiplier;\\n // Determines how frequently a new wallet creation can be requested.\\n // Value in seconds.\\n uint32 walletCreationPeriod;\\n // The minimum BTC threshold in satoshi that is used to decide about\\n // wallet creation. Specifically, we allow for the creation of a new\\n // wallet if the active wallet is old enough and their amount of BTC\\n // is greater than or equal this threshold.\\n uint64 walletCreationMinBtcBalance;\\n // The maximum BTC threshold in satoshi that is used to decide about\\n // wallet creation. Specifically, we allow for the creation of a new\\n // wallet if the active wallet's amount of BTC is greater than or equal\\n // this threshold, regardless of the active wallet's age.\\n uint64 walletCreationMaxBtcBalance;\\n // The minimum BTC threshold in satoshi that is used to decide about\\n // wallet closing. Specifically, we allow for the closure of the given\\n // wallet if their amount of BTC is lesser than this threshold,\\n // regardless of the wallet's age.\\n uint64 walletClosureMinBtcBalance;\\n // The maximum age of a wallet in seconds, after which the wallet\\n // moving funds process can be requested.\\n uint32 walletMaxAge;\\n // 20-byte wallet public key hash being reference to the currently\\n // active wallet. Can be unset to the zero value under certain\\n // circumstances.\\n bytes20 activeWalletPubKeyHash;\\n // The current number of wallets in the Live state.\\n uint32 liveWalletsCount;\\n // The maximum BTC amount in satoshi than can be transferred to a single\\n // target wallet during the moving funds process.\\n uint64 walletMaxBtcTransfer;\\n // Determines the length of the wallet closing period, i.e. the period\\n // when the wallet remains in the Closing state and can be subject\\n // of deposit fraud challenges. This value is in seconds and should be\\n // greater than the deposit refund time plus some time margin.\\n uint32 walletClosingPeriod;\\n // Collection of all revealed deposits indexed by\\n // `keccak256(fundingTxHash | fundingOutputIndex)`.\\n // The `fundingTxHash` is `bytes32` (ordered as in Bitcoin internally)\\n // and `fundingOutputIndex` an `uint32`. This mapping may contain valid\\n // and invalid deposits and the wallet is responsible for validating\\n // them before attempting to execute a sweep.\\n mapping(uint256 => Deposit.DepositRequest) deposits;\\n // Indicates if the vault with the given address is trusted.\\n // Depositors can route their revealed deposits only to trusted vaults\\n // and have trusted vaults notified about new deposits as soon as these\\n // deposits get swept. Vaults not trusted by the Bridge can still be\\n // used by Bank balance owners on their own responsibility - anyone can\\n // approve their Bank balance to any address.\\n mapping(address => bool) isVaultTrusted;\\n // Indicates if the address is a trusted SPV maintainer.\\n // The SPV proof does not check whether the transaction is a part of the\\n // Bitcoin mainnet, it only checks whether the transaction has been\\n // mined performing the required amount of work as on Bitcoin mainnet.\\n // The possibility of submitting SPV proofs is limited to trusted SPV\\n // maintainers. The system expects transaction confirmations with the\\n // required work accumulated, so trusted SPV maintainers can not prove\\n // the transaction without providing the required Bitcoin proof of work.\\n // Trusted maintainers address the issue of an economic game between\\n // tBTC and Bitcoin mainnet where large Bitcoin mining pools can decide\\n // to use their hash power to mine fake Bitcoin blocks to prove them in\\n // tBTC instead of receiving Bitcoin miner rewards.\\n mapping(address => bool) isSpvMaintainer;\\n // Collection of all moved funds sweep requests indexed by\\n // `keccak256(movingFundsTxHash | movingFundsOutputIndex)`.\\n // The `movingFundsTxHash` is `bytes32` (ordered as in Bitcoin\\n // internally) and `movingFundsOutputIndex` an `uint32`. Each entry\\n // is actually an UTXO representing the moved funds and is supposed\\n // to be swept with the current main UTXO of the recipient wallet.\\n mapping(uint256 => MovingFunds.MovedFundsSweepRequest) movedFundsSweepRequests;\\n // Collection of all pending redemption requests indexed by\\n // redemption key built as\\n // `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n // The `walletPubKeyHash` is the 20-byte wallet's public key hash\\n // (computed using Bitcoin HASH160 over the compressed ECDSA\\n // public key) and `redeemerOutputScript` is a Bitcoin script\\n // (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n // redeemed BTC as requested by the redeemer. Requests are added\\n // to this mapping by the `requestRedemption` method (duplicates\\n // not allowed) and are removed by one of the following methods:\\n // - `submitRedemptionProof` in case the request was handled\\n // successfully,\\n // - `notifyRedemptionTimeout` in case the request was reported\\n // to be timed out.\\n mapping(uint256 => Redemption.RedemptionRequest) pendingRedemptions;\\n // Collection of all timed out redemptions requests indexed by\\n // redemption key built as\\n // `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n // The `walletPubKeyHash` is the 20-byte wallet's public key hash\\n // (computed using Bitcoin HASH160 over the compressed ECDSA\\n // public key) and `redeemerOutputScript` is the Bitcoin script\\n // (P2PKH, P2WPKH, P2SH or P2WSH) that is involved in the timed\\n // out request.\\n // Only one method can add to this mapping:\\n // - `notifyRedemptionTimeout` which puts the redemption key to this\\n // mapping based on a timed out request stored previously in\\n // `pendingRedemptions` mapping.\\n // Only one method can remove entries from this mapping:\\n // - `submitRedemptionProof` in case the timed out redemption request\\n // was a part of the proven transaction.\\n mapping(uint256 => Redemption.RedemptionRequest) timedOutRedemptions;\\n // Collection of all submitted fraud challenges indexed by challenge\\n // key built as `keccak256(walletPublicKey|sighash)`.\\n mapping(uint256 => Fraud.FraudChallenge) fraudChallenges;\\n // Collection of main UTXOs that are honestly spent indexed by\\n // `keccak256(fundingTxHash | fundingOutputIndex)`. The `fundingTxHash`\\n // is `bytes32` (ordered as in Bitcoin internally) and\\n // `fundingOutputIndex` an `uint32`. A main UTXO is considered honestly\\n // spent if it was used as an input of a transaction that have been\\n // proven in the Bridge.\\n mapping(uint256 => bool) spentMainUTXOs;\\n // Maps the 20-byte wallet public key hash (computed using Bitcoin\\n // HASH160 over the compressed ECDSA public key) to the basic wallet\\n // information like state and pending redemptions value.\\n mapping(bytes20 => Wallets.Wallet) registeredWallets;\\n // Reserved storage space in case we need to add more variables.\\n // The convention from OpenZeppelin suggests the storage space should\\n // add up to 50 slots. Here we want to have more slots as there are\\n // planned upgrades of the Bridge contract. If more entires are added to\\n // the struct in the upcoming versions we need to reduce the array size.\\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n // slither-disable-next-line unused-state\\n uint256[50] __gap;\\n }\\n\\n event DepositParametersUpdated(\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n );\\n\\n event RedemptionParametersUpdated(\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n );\\n\\n event MovingFundsParametersUpdated(\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n\\n event WalletParametersUpdated(\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n );\\n\\n event FraudParametersUpdated(\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n );\\n\\n event TreasuryUpdated(address treasury);\\n\\n /// @notice Updates parameters of deposits.\\n /// @param _depositDustThreshold New value of the deposit dust threshold in\\n /// satoshis. It is the minimal amount that can be requested to\\n //// deposit. Value of this parameter must take into account the value\\n /// of `depositTreasuryFeeDivisor` and `depositTxMaxFee` parameters\\n /// in order to make requests that can incur the treasury and\\n /// transaction fee and still satisfy the depositor.\\n /// @param _depositTreasuryFeeDivisor New value of the treasury fee divisor.\\n /// It is the divisor used to compute the treasury fee taken from\\n /// each deposit and transferred to the treasury upon sweep proof\\n /// submission. That fee is computed as follows:\\n /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each deposit,\\n /// the `depositTreasuryFeeDivisor` should be set to `50`\\n /// because `1/50 = 0.02 = 2%`.\\n /// @param _depositTxMaxFee New value of the deposit tx max fee in satoshis.\\n /// It is the maximum amount of BTC transaction fee that can\\n /// be incurred by each swept deposit being part of the given sweep\\n /// transaction. If the maximum BTC transaction fee is exceeded,\\n /// such transaction is considered a fraud.\\n /// @param _depositRevealAheadPeriod New value of the deposit reveal ahead\\n /// period parameter in seconds. It defines the length of the period\\n /// that must be preserved between the deposit reveal time and the\\n /// deposit refund locktime.\\n /// @dev Requirements:\\n /// - Deposit dust threshold must be greater than zero,\\n /// - Deposit dust threshold must be greater than deposit TX max fee,\\n /// - Deposit transaction max fee must be greater than zero.\\n function updateDepositParameters(\\n Storage storage self,\\n uint64 _depositDustThreshold,\\n uint64 _depositTreasuryFeeDivisor,\\n uint64 _depositTxMaxFee,\\n uint32 _depositRevealAheadPeriod\\n ) internal {\\n require(\\n _depositDustThreshold > 0,\\n \\\"Deposit dust threshold must be greater than zero\\\"\\n );\\n\\n require(\\n _depositDustThreshold > _depositTxMaxFee,\\n \\\"Deposit dust threshold must be greater than deposit TX max fee\\\"\\n );\\n\\n require(\\n _depositTxMaxFee > 0,\\n \\\"Deposit transaction max fee must be greater than zero\\\"\\n );\\n\\n self.depositDustThreshold = _depositDustThreshold;\\n self.depositTreasuryFeeDivisor = _depositTreasuryFeeDivisor;\\n self.depositTxMaxFee = _depositTxMaxFee;\\n self.depositRevealAheadPeriod = _depositRevealAheadPeriod;\\n\\n emit DepositParametersUpdated(\\n _depositDustThreshold,\\n _depositTreasuryFeeDivisor,\\n _depositTxMaxFee,\\n _depositRevealAheadPeriod\\n );\\n }\\n\\n /// @notice Updates parameters of redemptions.\\n /// @param _redemptionDustThreshold New value of the redemption dust\\n /// threshold in satoshis. It is the minimal amount that can be\\n /// requested for redemption. Value of this parameter must take into\\n /// account the value of `redemptionTreasuryFeeDivisor` and\\n /// `redemptionTxMaxFee` parameters in order to make requests that\\n /// can incur the treasury and transaction fee and still satisfy the\\n /// redeemer.\\n /// @param _redemptionTreasuryFeeDivisor New value of the redemption\\n /// treasury fee divisor. It is the divisor used to compute the\\n /// treasury fee taken from each redemption request and transferred\\n /// to the treasury upon successful request finalization. That fee is\\n /// computed as follows:\\n /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each\\n /// redemption request, the `redemptionTreasuryFeeDivisor` should\\n /// be set to `50` because `1/50 = 0.02 = 2%`.\\n /// @param _redemptionTxMaxFee New value of the redemption transaction max\\n /// fee in satoshis. It is the maximum amount of BTC transaction fee\\n /// that can be incurred by each redemption request being part of the\\n /// given redemption transaction. If the maximum BTC transaction fee\\n /// is exceeded, such transaction is considered a fraud.\\n /// This is a per-redemption output max fee for the redemption\\n /// transaction.\\n /// @param _redemptionTxMaxTotalFee New value of the redemption transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single redemption\\n /// transaction. This is a _total_ max fee for the entire redemption\\n /// transaction.\\n /// @param _redemptionTimeout New value of the redemption timeout in seconds.\\n /// It is the time after which the redemption request can be reported\\n /// as timed out. It is counted from the moment when the redemption\\n /// request was created via `requestRedemption` call. Reported timed\\n /// out requests are cancelled and locked TBTC is returned to the\\n /// redeemer in full amount.\\n /// @param _redemptionTimeoutSlashingAmount New value of the redemption\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for redemption timeout.\\n /// @param _redemptionTimeoutNotifierRewardMultiplier New value of the\\n /// redemption timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a redemption timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Redemption dust threshold must be greater than moving funds dust\\n /// threshold,\\n /// - Redemption dust threshold must be greater than the redemption TX\\n /// max fee,\\n /// - Redemption transaction max fee must be greater than zero,\\n /// - Redemption transaction max total fee must be greater than or\\n /// equal to the redemption transaction per-request max fee,\\n /// - Redemption timeout must be greater than zero,\\n /// - Redemption timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateRedemptionParameters(\\n Storage storage self,\\n uint64 _redemptionDustThreshold,\\n uint64 _redemptionTreasuryFeeDivisor,\\n uint64 _redemptionTxMaxFee,\\n uint64 _redemptionTxMaxTotalFee,\\n uint32 _redemptionTimeout,\\n uint96 _redemptionTimeoutSlashingAmount,\\n uint32 _redemptionTimeoutNotifierRewardMultiplier\\n ) internal {\\n require(\\n _redemptionDustThreshold > self.movingFundsDustThreshold,\\n \\\"Redemption dust threshold must be greater than moving funds dust threshold\\\"\\n );\\n\\n require(\\n _redemptionDustThreshold > _redemptionTxMaxFee,\\n \\\"Redemption dust threshold must be greater than redemption TX max fee\\\"\\n );\\n\\n require(\\n _redemptionTxMaxFee > 0,\\n \\\"Redemption transaction max fee must be greater than zero\\\"\\n );\\n\\n require(\\n _redemptionTxMaxTotalFee >= _redemptionTxMaxFee,\\n \\\"Redemption transaction max total fee must be greater than or equal to the redemption transaction per-request max fee\\\"\\n );\\n\\n require(\\n _redemptionTimeout > 0,\\n \\\"Redemption timeout must be greater than zero\\\"\\n );\\n\\n require(\\n _redemptionTimeoutNotifierRewardMultiplier <= 100,\\n \\\"Redemption timeout notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n self.redemptionDustThreshold = _redemptionDustThreshold;\\n self.redemptionTreasuryFeeDivisor = _redemptionTreasuryFeeDivisor;\\n self.redemptionTxMaxFee = _redemptionTxMaxFee;\\n self.redemptionTxMaxTotalFee = _redemptionTxMaxTotalFee;\\n self.redemptionTimeout = _redemptionTimeout;\\n self.redemptionTimeoutSlashingAmount = _redemptionTimeoutSlashingAmount;\\n self\\n .redemptionTimeoutNotifierRewardMultiplier = _redemptionTimeoutNotifierRewardMultiplier;\\n\\n emit RedemptionParametersUpdated(\\n _redemptionDustThreshold,\\n _redemptionTreasuryFeeDivisor,\\n _redemptionTxMaxFee,\\n _redemptionTxMaxTotalFee,\\n _redemptionTimeout,\\n _redemptionTimeoutSlashingAmount,\\n _redemptionTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of moving funds.\\n /// @param _movingFundsTxMaxTotalFee New value of the moving funds transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single moving funds\\n /// transaction. This is a _total_ max fee for the entire moving\\n /// funds transaction.\\n /// @param _movingFundsDustThreshold New value of the moving funds dust\\n /// threshold. It is the minimal satoshi amount that makes sense to\\n /// be transferred during the moving funds process. Moving funds\\n /// wallets having their BTC balance below that value can begin\\n /// closing immediately as transferring such a low value may not be\\n /// possible due to BTC network fees.\\n /// @param _movingFundsTimeoutResetDelay New value of the moving funds\\n /// timeout reset delay in seconds. It is the time after which the\\n /// moving funds timeout can be reset in case the target wallet\\n /// commitment cannot be submitted due to a lack of live wallets\\n /// in the system. It is counted from the moment when the wallet\\n /// was requested to move their funds and switched to the MovingFunds\\n /// state or from the moment the timeout was reset the last time.\\n /// @param _movingFundsTimeout New value of the moving funds timeout in\\n /// seconds. It is the time after which the moving funds process can\\n /// be reported as timed out. It is counted from the moment when the\\n /// wallet was requested to move their funds and switched to the\\n /// MovingFunds state.\\n /// @param _movingFundsTimeoutSlashingAmount New value of the moving funds\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for moving funds timeout.\\n /// @param _movingFundsTimeoutNotifierRewardMultiplier New value of the\\n /// moving funds timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a moving funds timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @param _movingFundsCommitmentGasOffset New value of the gas offset for\\n /// moving funds target wallet commitment transaction gas costs\\n /// reimbursement.\\n /// @param _movedFundsSweepTxMaxTotalFee New value of the moved funds sweep\\n /// transaction max total fee in satoshis. It is the maximum amount\\n /// of the total BTC transaction fee that is acceptable in a single\\n /// moved funds sweep transaction. This is a _total_ max fee for the\\n /// entire moved funds sweep transaction.\\n /// @param _movedFundsSweepTimeout New value of the moved funds sweep\\n /// timeout in seconds. It is the time after which the moved funds\\n /// sweep process can be reported as timed out. It is counted from\\n /// the moment when the wallet was requested to sweep the received\\n /// funds.\\n /// @param _movedFundsSweepTimeoutSlashingAmount New value of the moved\\n /// funds sweep timeout slashing amount in T, it is the amount\\n /// slashed from each wallet member for moved funds sweep timeout.\\n /// @param _movedFundsSweepTimeoutNotifierRewardMultiplier New value of\\n /// the moved funds sweep timeout notifier reward multiplier as\\n /// percentage, it determines the percentage of the notifier reward\\n /// from the staking contact the notifier of a moved funds sweep\\n /// timeout receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Moving funds transaction max total fee must be greater than zero,\\n /// - Moving funds dust threshold must be greater than zero and lower\\n /// than the redemption dust threshold,\\n /// - Moving funds timeout reset delay must be greater than zero,\\n /// - Moving funds timeout must be greater than the moving funds\\n /// timeout reset delay,\\n /// - Moving funds timeout notifier reward multiplier must be in the\\n /// range [0, 100],\\n /// - Moved funds sweep transaction max total fee must be greater than zero,\\n /// - Moved funds sweep timeout must be greater than zero,\\n /// - Moved funds sweep timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateMovingFundsParameters(\\n Storage storage self,\\n uint64 _movingFundsTxMaxTotalFee,\\n uint64 _movingFundsDustThreshold,\\n uint32 _movingFundsTimeoutResetDelay,\\n uint32 _movingFundsTimeout,\\n uint96 _movingFundsTimeoutSlashingAmount,\\n uint32 _movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 _movingFundsCommitmentGasOffset,\\n uint64 _movedFundsSweepTxMaxTotalFee,\\n uint32 _movedFundsSweepTimeout,\\n uint96 _movedFundsSweepTimeoutSlashingAmount,\\n uint32 _movedFundsSweepTimeoutNotifierRewardMultiplier\\n ) internal {\\n require(\\n _movingFundsTxMaxTotalFee > 0,\\n \\\"Moving funds transaction max total fee must be greater than zero\\\"\\n );\\n\\n require(\\n _movingFundsDustThreshold > 0 &&\\n _movingFundsDustThreshold < self.redemptionDustThreshold,\\n \\\"Moving funds dust threshold must be greater than zero and lower than redemption dust threshold\\\"\\n );\\n\\n require(\\n _movingFundsTimeoutResetDelay > 0,\\n \\\"Moving funds timeout reset delay must be greater than zero\\\"\\n );\\n\\n require(\\n _movingFundsTimeout > _movingFundsTimeoutResetDelay,\\n \\\"Moving funds timeout must be greater than its reset delay\\\"\\n );\\n\\n require(\\n _movingFundsTimeoutNotifierRewardMultiplier <= 100,\\n \\\"Moving funds timeout notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n require(\\n _movedFundsSweepTxMaxTotalFee > 0,\\n \\\"Moved funds sweep transaction max total fee must be greater than zero\\\"\\n );\\n\\n require(\\n _movedFundsSweepTimeout > 0,\\n \\\"Moved funds sweep timeout must be greater than zero\\\"\\n );\\n\\n require(\\n _movedFundsSweepTimeoutNotifierRewardMultiplier <= 100,\\n \\\"Moved funds sweep timeout notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n self.movingFundsTxMaxTotalFee = _movingFundsTxMaxTotalFee;\\n self.movingFundsDustThreshold = _movingFundsDustThreshold;\\n self.movingFundsTimeoutResetDelay = _movingFundsTimeoutResetDelay;\\n self.movingFundsTimeout = _movingFundsTimeout;\\n self\\n .movingFundsTimeoutSlashingAmount = _movingFundsTimeoutSlashingAmount;\\n self\\n .movingFundsTimeoutNotifierRewardMultiplier = _movingFundsTimeoutNotifierRewardMultiplier;\\n self.movingFundsCommitmentGasOffset = _movingFundsCommitmentGasOffset;\\n self.movedFundsSweepTxMaxTotalFee = _movedFundsSweepTxMaxTotalFee;\\n self.movedFundsSweepTimeout = _movedFundsSweepTimeout;\\n self\\n .movedFundsSweepTimeoutSlashingAmount = _movedFundsSweepTimeoutSlashingAmount;\\n self\\n .movedFundsSweepTimeoutNotifierRewardMultiplier = _movedFundsSweepTimeoutNotifierRewardMultiplier;\\n\\n emit MovingFundsParametersUpdated(\\n _movingFundsTxMaxTotalFee,\\n _movingFundsDustThreshold,\\n _movingFundsTimeoutResetDelay,\\n _movingFundsTimeout,\\n _movingFundsTimeoutSlashingAmount,\\n _movingFundsTimeoutNotifierRewardMultiplier,\\n _movingFundsCommitmentGasOffset,\\n _movedFundsSweepTxMaxTotalFee,\\n _movedFundsSweepTimeout,\\n _movedFundsSweepTimeoutSlashingAmount,\\n _movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of wallets.\\n /// @param _walletCreationPeriod New value of the wallet creation period in\\n /// seconds, determines how frequently a new wallet creation can be\\n /// requested.\\n /// @param _walletCreationMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param _walletCreationMaxBtcBalance New value of the wallet maximum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param _walletClosureMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet closure.\\n /// @param _walletMaxAge New value of the wallet maximum age in seconds,\\n /// indicates the maximum age of a wallet in seconds, after which\\n /// the wallet moving funds process can be requested.\\n /// @param _walletMaxBtcTransfer New value of the wallet maximum BTC transfer\\n /// in satoshi, determines the maximum amount that can be transferred\\n /// to a single target wallet during the moving funds process.\\n /// @param _walletClosingPeriod New value of the wallet closing period in\\n /// seconds, determines the length of the wallet closing period,\\n // i.e. the period when the wallet remains in the Closing state\\n // and can be subject of deposit fraud challenges.\\n /// @dev Requirements:\\n /// - Wallet maximum BTC balance must be greater than the wallet\\n /// minimum BTC balance,\\n /// - Wallet maximum BTC transfer must be greater than zero,\\n /// - Wallet closing period must be greater than zero.\\n function updateWalletParameters(\\n Storage storage self,\\n uint32 _walletCreationPeriod,\\n uint64 _walletCreationMinBtcBalance,\\n uint64 _walletCreationMaxBtcBalance,\\n uint64 _walletClosureMinBtcBalance,\\n uint32 _walletMaxAge,\\n uint64 _walletMaxBtcTransfer,\\n uint32 _walletClosingPeriod\\n ) internal {\\n require(\\n _walletCreationMaxBtcBalance > _walletCreationMinBtcBalance,\\n \\\"Wallet creation maximum BTC balance must be greater than the creation minimum BTC balance\\\"\\n );\\n require(\\n _walletMaxBtcTransfer > 0,\\n \\\"Wallet maximum BTC transfer must be greater than zero\\\"\\n );\\n require(\\n _walletClosingPeriod > 0,\\n \\\"Wallet closing period must be greater than zero\\\"\\n );\\n\\n self.walletCreationPeriod = _walletCreationPeriod;\\n self.walletCreationMinBtcBalance = _walletCreationMinBtcBalance;\\n self.walletCreationMaxBtcBalance = _walletCreationMaxBtcBalance;\\n self.walletClosureMinBtcBalance = _walletClosureMinBtcBalance;\\n self.walletMaxAge = _walletMaxAge;\\n self.walletMaxBtcTransfer = _walletMaxBtcTransfer;\\n self.walletClosingPeriod = _walletClosingPeriod;\\n\\n emit WalletParametersUpdated(\\n _walletCreationPeriod,\\n _walletCreationMinBtcBalance,\\n _walletCreationMaxBtcBalance,\\n _walletClosureMinBtcBalance,\\n _walletMaxAge,\\n _walletMaxBtcTransfer,\\n _walletClosingPeriod\\n );\\n }\\n\\n /// @notice Updates parameters related to frauds.\\n /// @param _fraudChallengeDepositAmount New value of the fraud challenge\\n /// deposit amount in wei, it is the amount of ETH the party\\n /// challenging the wallet for fraud needs to deposit.\\n /// @param _fraudChallengeDefeatTimeout New value of the challenge defeat\\n /// timeout in seconds, it is the amount of time the wallet has to\\n /// defeat a fraud challenge. The value must be greater than zero.\\n /// @param _fraudSlashingAmount New value of the fraud slashing amount in T,\\n /// it is the amount slashed from each wallet member for committing\\n /// a fraud.\\n /// @param _fraudNotifierRewardMultiplier New value of the fraud notifier\\n /// reward multiplier as percentage, it determines the percentage of\\n /// the notifier reward from the staking contact the notifier of\\n /// a fraud receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Fraud challenge defeat timeout must be greater than 0,\\n /// - Fraud notifier reward multiplier must be in the range [0, 100].\\n function updateFraudParameters(\\n Storage storage self,\\n uint96 _fraudChallengeDepositAmount,\\n uint32 _fraudChallengeDefeatTimeout,\\n uint96 _fraudSlashingAmount,\\n uint32 _fraudNotifierRewardMultiplier\\n ) internal {\\n require(\\n _fraudChallengeDefeatTimeout > 0,\\n \\\"Fraud challenge defeat timeout must be greater than zero\\\"\\n );\\n\\n require(\\n _fraudNotifierRewardMultiplier <= 100,\\n \\\"Fraud notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n self.fraudChallengeDepositAmount = _fraudChallengeDepositAmount;\\n self.fraudChallengeDefeatTimeout = _fraudChallengeDefeatTimeout;\\n self.fraudSlashingAmount = _fraudSlashingAmount;\\n self.fraudNotifierRewardMultiplier = _fraudNotifierRewardMultiplier;\\n\\n emit FraudParametersUpdated(\\n _fraudChallengeDepositAmount,\\n _fraudChallengeDefeatTimeout,\\n _fraudSlashingAmount,\\n _fraudNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates treasury address. The treasury receives the system fees.\\n /// @param _treasury New value of the treasury address.\\n /// @dev The treasury address must not be 0x0.\\n function updateTreasury(Storage storage self, address _treasury) internal {\\n require(_treasury != address(0), \\\"Treasury address must not be 0x0\\\");\\n\\n self.treasury = _treasury;\\n emit TreasuryUpdated(_treasury);\\n }\\n}\\n\",\"keccak256\":\"0x0fe63c47a08ff0e56bd3bf4d2c9db4b09df717ac77ba5b18ab382a1e0bbb11c7\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Deposit.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\n/// @title Bridge deposit\\n/// @notice The library handles the logic for revealing Bitcoin deposits to\\n/// the Bridge.\\n/// @dev The depositor puts together a P2SH or P2WSH address to deposit the\\n/// funds. This script is unique to each depositor and looks like this:\\n///\\n/// ```\\n/// DROP\\n/// DROP\\n/// DUP HASH160 EQUAL\\n/// IF\\n/// CHECKSIG\\n/// ELSE\\n/// DUP HASH160 EQUALVERIFY\\n/// CHECKLOCKTIMEVERIFY DROP\\n/// CHECKSIG\\n/// ENDIF\\n/// ```\\n///\\n/// Since each depositor has their own Ethereum address and their own\\n/// blinding factor, each depositor\\u2019s script is unique, and the hash\\n/// of each depositor\\u2019s script is unique.\\nlibrary Deposit {\\n using BTCUtils for bytes;\\n using BytesLib for bytes;\\n\\n /// @notice Represents data which must be revealed by the depositor during\\n /// deposit reveal.\\n struct DepositRevealInfo {\\n // Index of the funding output belonging to the funding transaction.\\n uint32 fundingOutputIndex;\\n // The blinding factor as 8 bytes. Byte endianness doesn't matter\\n // as this factor is not interpreted as uint. The blinding factor allows\\n // to distinguish deposits from the same depositor.\\n bytes8 blindingFactor;\\n // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)\\n // of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.\\n bytes20 walletPubKeyHash;\\n // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)\\n // that can be used to make the deposit refund after the refund\\n // locktime passes. Hashed in the HASH160 Bitcoin opcode style.\\n bytes20 refundPubKeyHash;\\n // The refund locktime (4-byte LE). Interpreted according to locktime\\n // parsing rules described in:\\n // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number\\n // and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:\\n // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki\\n bytes4 refundLocktime;\\n // Address of the Bank vault to which the deposit is routed to.\\n // Optional, can be 0x0. The vault must be trusted by the Bridge.\\n address vault;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\n }\\n\\n /// @notice Represents tBTC deposit request data.\\n struct DepositRequest {\\n // Ethereum depositor address.\\n address depositor;\\n // Deposit amount in satoshi.\\n uint64 amount;\\n // UNIX timestamp the deposit was revealed at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 revealedAt;\\n // Address of the Bank vault the deposit is routed to.\\n // Optional, can be 0x0.\\n address vault;\\n // Treasury TBTC fee in satoshi at the moment of deposit reveal.\\n uint64 treasuryFee;\\n // UNIX timestamp the deposit was swept at. Note this is not the\\n // time when the deposit was swept on the Bitcoin chain but actually\\n // the time when the sweep proof was delivered to the Ethereum chain.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 sweptAt;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event DepositRevealed(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex,\\n address indexed depositor,\\n uint64 amount,\\n bytes8 blindingFactor,\\n bytes20 indexed walletPubKeyHash,\\n bytes20 refundPubKeyHash,\\n bytes4 refundLocktime,\\n address vault\\n );\\n\\n /// @notice Used by the depositor to reveal information about their P2(W)SH\\n /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain\\n /// wallet listens for revealed deposit events and may decide to\\n /// include the revealed deposit in the next executed sweep.\\n /// Information about the Bitcoin deposit can be revealed before or\\n /// after the Bitcoin transaction with P2(W)SH deposit is mined on\\n /// the Bitcoin chain. Worth noting, the gas cost of this function\\n /// scales with the number of P2(W)SH transaction inputs and\\n /// outputs. The deposit may be routed to one of the trusted vaults.\\n /// When a deposit is routed to a vault, vault gets notified when\\n /// the deposit gets swept and it may execute the appropriate action.\\n /// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`.\\n /// @param reveal Deposit reveal data, see `RevealInfo struct.\\n /// @dev Requirements:\\n /// - This function must be called by the same Ethereum address as the\\n /// one used in the P2(W)SH BTC deposit transaction as a depositor,\\n /// - `reveal.walletPubKeyHash` must identify a `Live` wallet,\\n /// - `reveal.vault` must be 0x0 or point to a trusted vault,\\n /// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH\\n /// output of the BTC deposit transaction,\\n /// - `reveal.blindingFactor` must be the blinding factor used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundPubKeyHash` must be the refund pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundLocktime` must be the refund locktime used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`\\n /// can be revealed only one time.\\n ///\\n /// If any of these requirements is not met, the wallet _must_ refuse\\n /// to sweep the deposit and the depositor has to wait until the\\n /// deposit script unlocks to receive their BTC back.\\n function revealDeposit(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata fundingTx,\\n DepositRevealInfo calldata reveal\\n ) external {\\n require(\\n self.registeredWallets[reveal.walletPubKeyHash].state ==\\n Wallets.WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n require(\\n reveal.vault == address(0) || self.isVaultTrusted[reveal.vault],\\n \\\"Vault is not trusted\\\"\\n );\\n\\n if (self.depositRevealAheadPeriod > 0) {\\n validateDepositRefundLocktime(self, reveal.refundLocktime);\\n }\\n\\n bytes memory expectedScript = abi.encodePacked(\\n hex\\\"14\\\", // Byte length of depositor Ethereum address.\\n msg.sender,\\n hex\\\"75\\\", // OP_DROP\\n hex\\\"08\\\", // Byte length of blinding factor value.\\n reveal.blindingFactor,\\n hex\\\"75\\\", // OP_DROP\\n hex\\\"76\\\", // OP_DUP\\n hex\\\"a9\\\", // OP_HASH160\\n hex\\\"14\\\", // Byte length of a compressed Bitcoin public key hash.\\n reveal.walletPubKeyHash,\\n hex\\\"87\\\", // OP_EQUAL\\n hex\\\"63\\\", // OP_IF\\n hex\\\"ac\\\", // OP_CHECKSIG\\n hex\\\"67\\\", // OP_ELSE\\n hex\\\"76\\\", // OP_DUP\\n hex\\\"a9\\\", // OP_HASH160\\n hex\\\"14\\\", // Byte length of a compressed Bitcoin public key hash.\\n reveal.refundPubKeyHash,\\n hex\\\"88\\\", // OP_EQUALVERIFY\\n hex\\\"04\\\", // Byte length of refund locktime value.\\n reveal.refundLocktime,\\n hex\\\"b1\\\", // OP_CHECKLOCKTIMEVERIFY\\n hex\\\"75\\\", // OP_DROP\\n hex\\\"ac\\\", // OP_CHECKSIG\\n hex\\\"68\\\" // OP_ENDIF\\n );\\n\\n bytes memory fundingOutput = fundingTx\\n .outputVector\\n .extractOutputAtIndex(reveal.fundingOutputIndex);\\n bytes memory fundingOutputHash = fundingOutput.extractHash();\\n\\n if (fundingOutputHash.length == 20) {\\n // A 20-byte output hash is used by P2SH. That hash is constructed\\n // by applying OP_HASH160 on the locking script. A 20-byte output\\n // hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the\\n // public key). However, since we compare the actual output hash\\n // with an expected locking script hash, this check will succeed only\\n // for P2SH transaction type with expected script hash value. For\\n // P2PKH and P2WPKH, it will fail on the output hash comparison with\\n // the expected locking script hash.\\n require(\\n fundingOutputHash.slice20(0) == expectedScript.hash160View(),\\n \\\"Wrong 20-byte script hash\\\"\\n );\\n } else if (fundingOutputHash.length == 32) {\\n // A 32-byte output hash is used by P2WSH. That hash is constructed\\n // by applying OP_SHA256 on the locking script.\\n require(\\n fundingOutputHash.toBytes32() == sha256(expectedScript),\\n \\\"Wrong 32-byte script hash\\\"\\n );\\n } else {\\n revert(\\\"Wrong script hash length\\\");\\n }\\n\\n // Resulting TX hash is in native Bitcoin little-endian format.\\n bytes32 fundingTxHash = abi\\n .encodePacked(\\n fundingTx.version,\\n fundingTx.inputVector,\\n fundingTx.outputVector,\\n fundingTx.locktime\\n )\\n .hash256View();\\n\\n DepositRequest storage deposit = self.deposits[\\n uint256(\\n keccak256(\\n abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)\\n )\\n )\\n ];\\n require(deposit.revealedAt == 0, \\\"Deposit already revealed\\\");\\n\\n uint64 fundingOutputAmount = fundingOutput.extractValue();\\n\\n require(\\n fundingOutputAmount >= self.depositDustThreshold,\\n \\\"Deposit amount too small\\\"\\n );\\n\\n deposit.amount = fundingOutputAmount;\\n deposit.depositor = msg.sender;\\n /* solhint-disable-next-line not-rely-on-time */\\n deposit.revealedAt = uint32(block.timestamp);\\n deposit.vault = reveal.vault;\\n deposit.treasuryFee = self.depositTreasuryFeeDivisor > 0\\n ? fundingOutputAmount / self.depositTreasuryFeeDivisor\\n : 0;\\n // slither-disable-next-line reentrancy-events\\n emit DepositRevealed(\\n fundingTxHash,\\n reveal.fundingOutputIndex,\\n msg.sender,\\n fundingOutputAmount,\\n reveal.blindingFactor,\\n reveal.walletPubKeyHash,\\n reveal.refundPubKeyHash,\\n reveal.refundLocktime,\\n reveal.vault\\n );\\n }\\n\\n /// @notice Validates the deposit refund locktime. The validation passes\\n /// successfully only if the deposit reveal is done respectively\\n /// earlier than the moment when the deposit refund locktime is\\n /// reached, i.e. the deposit become refundable. Reverts otherwise.\\n /// @param refundLocktime The deposit refund locktime as 4-byte LE.\\n /// @dev Requirements:\\n /// - `refundLocktime` as integer must be >= 500M\\n /// - `refundLocktime` must denote a timestamp that is at least\\n /// `depositRevealAheadPeriod` seconds later than the moment\\n /// of `block.timestamp`\\n function validateDepositRefundLocktime(\\n BridgeState.Storage storage self,\\n bytes4 refundLocktime\\n ) internal view {\\n // Convert the refund locktime byte array to a LE integer. This is\\n // the moment in time when the deposit become refundable.\\n uint32 depositRefundableTimestamp = BTCUtils.reverseUint32(\\n uint32(refundLocktime)\\n );\\n // According to https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number\\n // the locktime is parsed as a block number if less than 500M. We always\\n // want to parse the locktime as an Unix timestamp so we allow only for\\n // values bigger than or equal to 500M.\\n require(\\n depositRefundableTimestamp >= 500 * 1e6,\\n \\\"Refund locktime must be a value >= 500M\\\"\\n );\\n // The deposit must be revealed before it becomes refundable.\\n // This is because the sweeping wallet needs to have some time to\\n // sweep the deposit and avoid a potential competition with the\\n // depositor making the deposit refund.\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp + self.depositRevealAheadPeriod <=\\n depositRefundableTimestamp,\\n \\\"Deposit refund locktime is too close\\\"\\n );\\n }\\n}\\n\",\"keccak256\":\"0xc00d41ea9e98a6fa3d8d8701ad8554c459e0268c6bf413f0447bc1c68c6ab7e8\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/DepositSweep.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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 temporary information needed during the processing\\n /// of the deposit sweep Bitcoin transaction inputs. This structure\\n /// is an internal one and should not be exported outside of the\\n /// deposit sweep transaction processing code.\\n /// @dev Allows to mitigate \\\"stack too deep\\\" errors on EVM.\\n struct DepositSweepTxInputsProcessingInfo {\\n // Input vector of the deposit sweep Bitcoin transaction. It is\\n // assumed the vector's structure is valid so it must be validated\\n // using e.g. `BTCUtils.validateVin` function before being used\\n // during the processing. The validation is usually done as part\\n // of the `BitcoinTx.validateProof` call that checks the SPV proof.\\n bytes sweepTxInputVector;\\n // Data of the wallet's main UTXO. If no main UTXO exists for the given\\n // sweeping wallet, this parameter's fields should be zeroed to bypass\\n // the main UTXO validation\\n BitcoinTx.UTXO mainUtxo;\\n // Address of the vault where all swept deposits should be routed to.\\n // It is used to validate whether all swept deposits have been revealed\\n // with the same `vault` parameter. It is an optional parameter.\\n // Set to zero address if deposits are not routed to a vault.\\n address vault;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\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 /// @param vault Optional address of the vault where all swept deposits\\n /// should be routed to. All deposits swept as part of the transaction\\n /// must have their `vault` parameters set to the same address.\\n /// If this parameter is set to an address of a trusted vault, swept\\n /// deposits are routed to that vault.\\n /// If this parameter is set to the zero address or to an address\\n /// of a non-trusted vault, swept deposits are not routed to a\\n /// vault but depositors' balances are increased in the Bank\\n /// individually.\\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 /// - All revealed deposits that are swept by `sweepTx` must have\\n /// their `vault` parameters set to the same address as the address\\n /// passed in the `vault` function parameter,\\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 address vault\\n ) external {\\n // Wallet state validation is performed in the\\n // `resolveDepositSweepingWallet` function.\\n\\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 DepositSweepTxInputsProcessingInfo(\\n sweepTx.inputVector,\\n resolvedMainUtxo,\\n vault\\n )\\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 if (vault != address(0) && self.isVaultTrusted[vault]) {\\n // If the `vault` address is not zero and belongs to a trusted\\n // vault, route the deposits to that vault.\\n self.bank.increaseBalanceAndCall(\\n vault,\\n inputsInfo.depositors,\\n inputsInfo.depositedAmounts\\n );\\n } else {\\n // If the `vault` address is zero or belongs to a non-trusted\\n // vault, increase balances in the Bank individually for each\\n // depositor.\\n self.bank.increaseBalances(\\n inputsInfo.depositors,\\n inputsInfo.depositedAmounts\\n );\\n }\\n\\n // Pass the treasury fee to the treasury address.\\n if (totalTreasuryFee > 0) {\\n self.bank.increaseBalance(self.treasury, totalTreasuryFee);\\n }\\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 /// @return resultInfo Outcomes of the processing.\\n function processDepositSweepTxInputs(\\n BridgeState.Storage storage self,\\n DepositSweepTxInputsProcessingInfo memory processInfo\\n ) internal returns (DepositSweepTxInputsInfo memory resultInfo) {\\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 = processInfo.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 (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = processInfo\\n .sweepTxInputVector\\n .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 resultInfo.depositors = new address[](\\n !mainUtxoExpected ? inputsCount : inputsCount - 1\\n );\\n resultInfo.depositedAmounts = new uint256[](\\n resultInfo.depositors.length\\n );\\n resultInfo.treasuryFees = new uint256[](resultInfo.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 processInfo.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 require(\\n deposit.vault == processInfo.vault,\\n \\\"Deposit should be routed to another vault\\\"\\n );\\n\\n if (processedDepositsCount == resultInfo.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 resultInfo.depositors[processedDepositsCount] = deposit\\n .depositor;\\n resultInfo.depositedAmounts[processedDepositsCount] = deposit\\n .amount;\\n resultInfo.inputsTotalValue += resultInfo.depositedAmounts[\\n processedDepositsCount\\n ];\\n resultInfo.treasuryFees[processedDepositsCount] = deposit\\n .treasuryFee;\\n\\n processedDepositsCount++;\\n } else if (\\n mainUtxoExpected != mainUtxoFound &&\\n processInfo.mainUtxo.txHash == outpointTxHash &&\\n processInfo.mainUtxo.txOutputIndex == outpointIndex\\n ) {\\n // If we entered here, that means the input was identified as\\n // the expected main UTXO.\\n resultInfo.inputsTotalValue += processInfo\\n .mainUtxo\\n .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 == resultInfo.depositors.length == resultInfo.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 resultInfo;\\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\",\"keccak256\":\"0xa5b6319960c51c1c5b1edf0539075b822e70d43035eb5f1d20325d0b57cbd73c\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/EcdsaLib.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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\",\"keccak256\":\"0x670d0fbeb088f78abfe7ae7f844784d774ca515480383378a602af707cace7ff\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Fraud.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {CheckBitcoinSigs} from \\\"@keep-network/bitcoin-spv-sol/contracts/CheckBitcoinSigs.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./EcdsaLib.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Heartbeat.sol\\\";\\nimport \\\"./MovingFunds.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\n/// @title Bridge fraud\\n/// @notice The library handles the logic for challenging Bridge wallets that\\n/// committed fraud.\\n/// @dev Anyone can submit a fraud challenge indicating that a UTXO being under\\n/// the wallet control was unlocked by the wallet but was not used\\n/// according to the protocol rules. That means the wallet signed\\n/// a transaction input pointing to that UTXO and there is a unique\\n/// sighash and signature pair associated with that input.\\n///\\n/// In order to defeat the challenge, the same wallet public key and\\n/// signature must be provided as were used to calculate the sighash during\\n/// the challenge. The wallet provides the preimage which produces sighash\\n/// used to generate the ECDSA signature that is the subject of the fraud\\n/// claim.\\n///\\n/// The fraud challenge defeat attempt will succeed if the inputs in the\\n/// preimage are considered honestly spent by the wallet. Therefore the\\n/// transaction spending the UTXO must be proven in the Bridge before\\n/// a challenge defeat is called.\\n///\\n/// Another option is when a malicious wallet member used a signed heartbeat\\n/// message periodically produced by the wallet off-chain to challenge the\\n/// wallet for a fraud. Anyone from the wallet can defeat the challenge by\\n/// proving the sighash and signature were produced for a heartbeat message\\n/// following a strict format.\\nlibrary Fraud {\\n using Wallets for BridgeState.Storage;\\n\\n using BytesLib for bytes;\\n using BTCUtils for bytes;\\n using BTCUtils for uint32;\\n using EcdsaLib for bytes;\\n\\n struct FraudChallenge {\\n // The address of the party challenging the wallet.\\n address challenger;\\n // The amount of ETH the challenger deposited.\\n uint256 depositAmount;\\n // The timestamp the challenge was submitted at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 reportedAt;\\n // The flag indicating whether the challenge has been resolved.\\n bool resolved;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event FraudChallengeSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n );\\n\\n event FraudChallengeDefeated(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash\\n );\\n\\n event FraudChallengeDefeatTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n // Sighash calculated as a Bitcoin's hash256 (double sha2) of:\\n // - a preimage of a transaction spending UTXO according to the protocol\\n // rules OR\\n // - a valid heartbeat message produced by the wallet off-chain.\\n bytes32 sighash\\n );\\n\\n /// @notice Submits a fraud challenge indicating that a UTXO being under\\n /// wallet control was unlocked by the wallet but was not used\\n /// according to the protocol rules. That means the wallet signed\\n /// a transaction input pointing to that UTXO and there is a unique\\n /// sighash and signature pair associated with that input. This\\n /// function uses those parameters to create a fraud accusation that\\n /// proves a given transaction input unlocking the given UTXO was\\n /// actually signed by the wallet. This function cannot determine\\n /// whether the transaction was actually broadcast and the input was\\n /// consumed in a fraudulent way so it just opens a challenge period\\n /// during which the wallet can defeat the challenge by submitting\\n /// proof of a transaction that consumes the given input according\\n /// to protocol rules. To prevent spurious allegations, the caller\\n /// must deposit ETH that is returned back upon justified fraud\\n /// challenge or confiscated otherwise.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @param signature Bitcoin signature in the R/S/V format\\n /// @dev Requirements:\\n /// - Wallet behind `walletPublicKey` must be in Live or MovingFunds\\n /// or Closing state,\\n /// - The challenger must send appropriate amount of ETH used as\\n /// fraud challenge deposit,\\n /// - The signature (represented by r, s and v) must be generated by\\n /// the wallet behind `walletPubKey` during signing of `sighash`\\n /// which was calculated from `preimageSha256`,\\n /// - Wallet can be challenged for the given signature only once.\\n function submitFraudChallenge(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n bytes memory preimageSha256,\\n BitcoinTx.RSVSignature calldata signature\\n ) external {\\n require(\\n msg.value >= self.fraudChallengeDepositAmount,\\n \\\"The amount of ETH deposited is too low\\\"\\n );\\n\\n // To prevent ECDSA signature forgery `sighash` must be calculated\\n // inside the function and not passed as a function parameter.\\n // Signature forgery could result in a wrongful fraud accusation\\n // against a wallet.\\n bytes32 sighash = sha256(preimageSha256);\\n\\n require(\\n CheckBitcoinSigs.checkSig(\\n walletPublicKey,\\n sighash,\\n signature.v,\\n signature.r,\\n signature.s\\n ),\\n \\\"Signature verification failure\\\"\\n );\\n\\n bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(\\n walletPublicKey.slice32(0),\\n walletPublicKey.slice32(32)\\n );\\n bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();\\n\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.Live ||\\n wallet.state == Wallets.WalletState.MovingFunds ||\\n wallet.state == Wallets.WalletState.Closing,\\n \\\"Wallet must be in Live or MovingFunds or Closing state\\\"\\n );\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n require(challenge.reportedAt == 0, \\\"Fraud challenge already exists\\\");\\n\\n challenge.challenger = msg.sender;\\n challenge.depositAmount = msg.value;\\n /* solhint-disable-next-line not-rely-on-time */\\n challenge.reportedAt = uint32(block.timestamp);\\n challenge.resolved = false;\\n // slither-disable-next-line reentrancy-events\\n emit FraudChallengeSubmitted(\\n walletPubKeyHash,\\n sighash,\\n signature.v,\\n signature.r,\\n signature.s\\n );\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet if\\n /// the transaction that spends the UTXO follows the protocol rules.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during input signing.\\n /// The fraud challenge defeat attempt will only succeed if the\\n /// inputs in the preimage are considered honestly spent by the\\n /// wallet. Therefore the transaction spending the UTXO must be\\n /// proven in the Bridge before a challenge defeat is called.\\n /// If successfully defeated, the fraud challenge is marked as\\n /// resolved and the amount of ether deposited by the challenger is\\n /// sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference.\\n /// @param witness Flag indicating whether the preimage was produced for a\\n /// witness input. True for witness, false for non-witness input.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`\\n /// must identify an open fraud challenge,\\n /// - the preimage must be a valid preimage of a transaction generated\\n /// according to the protocol rules and already proved in the Bridge,\\n /// - before a defeat attempt is made the transaction that spends the\\n /// given UTXO must be proven in the Bridge.\\n function defeatFraudChallenge(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n bytes calldata preimage,\\n bool witness\\n ) external {\\n bytes32 sighash = preimage.hash256();\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n\\n require(challenge.reportedAt > 0, \\\"Fraud challenge does not exist\\\");\\n require(\\n !challenge.resolved,\\n \\\"Fraud challenge has already been resolved\\\"\\n );\\n\\n // Ensure SIGHASH_ALL type was used during signing, which is represented\\n // by type value `1`.\\n require(extractSighashType(preimage) == 1, \\\"Wrong sighash type\\\");\\n\\n uint256 utxoKey = witness\\n ? extractUtxoKeyFromWitnessPreimage(preimage)\\n : extractUtxoKeyFromNonWitnessPreimage(preimage);\\n\\n // Check that the UTXO key identifies a correctly spent UTXO.\\n require(\\n self.deposits[utxoKey].sweptAt > 0 ||\\n self.spentMainUTXOs[utxoKey] ||\\n self.movedFundsSweepRequests[utxoKey].state ==\\n MovingFunds.MovedFundsSweepRequestState.Processed,\\n \\\"Spent UTXO not found among correctly spent UTXOs\\\"\\n );\\n\\n resolveFraudChallenge(self, walletPublicKey, challenge, sighash);\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet by\\n /// proving the sighash and signature were produced for an off-chain\\n /// wallet heartbeat message following a strict format.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during heartbeat message\\n /// signing. The fraud challenge defeat attempt will only succeed if\\n /// the signed message follows a strict format required for\\n /// heartbeat messages. If successfully defeated, the fraud\\n /// challenge is marked as resolved and the amount of ether\\n /// deposited by the challenger is sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes),\\n /// @param heartbeatMessage Off-chain heartbeat message meeting the heartbeat\\n /// message format requirements which produces sighash used to\\n /// generate the ECDSA signature that is the subject of the fraud\\n /// claim.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as\\n /// `hash256(heartbeatMessage)` must identify an open fraud challenge,\\n /// - `heartbeatMessage` must follow a strict format of heartbeat\\n /// messages.\\n function defeatFraudChallengeWithHeartbeat(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n bytes calldata heartbeatMessage\\n ) external {\\n bytes32 sighash = heartbeatMessage.hash256();\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n\\n require(challenge.reportedAt > 0, \\\"Fraud challenge does not exist\\\");\\n require(\\n !challenge.resolved,\\n \\\"Fraud challenge has already been resolved\\\"\\n );\\n\\n require(\\n Heartbeat.isValidHeartbeatMessage(heartbeatMessage),\\n \\\"Not a valid heartbeat message\\\"\\n );\\n\\n resolveFraudChallenge(self, walletPublicKey, challenge, sighash);\\n }\\n\\n /// @notice Called only for successfully defeated fraud challenges.\\n /// The fraud challenge is marked as resolved and the amount of\\n /// ether deposited by the challenger is sent to the treasury.\\n /// @dev Requirements:\\n /// - Must be called only for successfully defeated fraud challenges.\\n function resolveFraudChallenge(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n FraudChallenge storage challenge,\\n bytes32 sighash\\n ) internal {\\n // Mark the challenge as resolved as it was successfully defeated\\n challenge.resolved = true;\\n\\n // Send the ether deposited by the challenger to the treasury\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,unchecked-lowlevel,arbitrary-send\\n self.treasury.call{gas: 100000, value: challenge.depositAmount}(\\\"\\\");\\n /* solhint-enable avoid-low-level-calls */\\n\\n bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(\\n walletPublicKey.slice32(0),\\n walletPublicKey.slice32(32)\\n );\\n bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();\\n\\n // slither-disable-next-line reentrancy-events\\n emit FraudChallengeDefeated(walletPubKeyHash, sighash);\\n }\\n\\n /// @notice Notifies about defeat timeout for the given fraud challenge.\\n /// Can be called only if there was a fraud challenge identified by\\n /// the provided `walletPublicKey` and `sighash` and it was not\\n /// defeated on time. The amount of time that needs to pass after\\n /// a fraud challenge is reported is indicated by the\\n /// `challengeDefeatTimeout`. After a successful fraud challenge\\n /// defeat timeout notification the fraud challenge is marked as\\n /// resolved, the stake of each operator is slashed, the ether\\n /// deposited is returned to the challenger and the challenger is\\n /// rewarded.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Closing or\\n /// Terminated state,\\n /// - The `walletPublicKey` and `sighash` calculated from\\n /// `preimageSha256` must identify an open fraud challenge,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time indicated by `challengeDefeatTimeout` must pass\\n /// after the challenge was reported.\\n function notifyFraudChallengeDefeatTimeout(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n uint32[] calldata walletMembersIDs,\\n bytes memory preimageSha256\\n ) external {\\n // Wallet state is validated in `notifyWalletFraudChallengeDefeatTimeout`.\\n\\n bytes32 sighash = sha256(preimageSha256);\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n\\n require(challenge.reportedAt > 0, \\\"Fraud challenge does not exist\\\");\\n\\n require(\\n !challenge.resolved,\\n \\\"Fraud challenge has already been resolved\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >=\\n challenge.reportedAt + self.fraudChallengeDefeatTimeout,\\n \\\"Fraud challenge defeat period did not time out yet\\\"\\n );\\n\\n challenge.resolved = true;\\n // Return the ether deposited by the challenger\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,unchecked-lowlevel\\n challenge.challenger.call{gas: 100000, value: challenge.depositAmount}(\\n \\\"\\\"\\n );\\n /* solhint-enable avoid-low-level-calls */\\n\\n bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(\\n walletPublicKey.slice32(0),\\n walletPublicKey.slice32(32)\\n );\\n bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();\\n\\n self.notifyWalletFraudChallengeDefeatTimeout(\\n walletPubKeyHash,\\n walletMembersIDs,\\n challenge.challenger\\n );\\n\\n // slither-disable-next-line reentrancy-events\\n emit FraudChallengeDefeatTimedOut(walletPubKeyHash, sighash);\\n }\\n\\n /// @notice Extracts the UTXO keys from the given preimage used during\\n /// signing of a witness input.\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference\\n /// @return utxoKey UTXO key that identifies spent input.\\n function extractUtxoKeyFromWitnessPreimage(bytes calldata preimage)\\n internal\\n pure\\n returns (uint256 utxoKey)\\n {\\n // The expected structure of the preimage created during signing of a\\n // witness input:\\n // - transaction version (4 bytes)\\n // - hash of previous outpoints of all inputs (32 bytes)\\n // - hash of sequences of all inputs (32 bytes)\\n // - outpoint (hash + index) of the input being signed (36 bytes)\\n // - the unlocking script of the input (variable length)\\n // - value of the outpoint (8 bytes)\\n // - sequence of the input being signed (4 bytes)\\n // - hash of all outputs (32 bytes)\\n // - transaction locktime (4 bytes)\\n // - sighash type (4 bytes)\\n\\n // See Bitcoin's BIP-143 for reference:\\n // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.\\n\\n // The outpoint (hash and index) is located at the constant offset of\\n // 68 (4 + 32 + 32).\\n bytes32 outpointTxHash = preimage.extractInputTxIdLeAt(68);\\n uint32 outpointIndex = BTCUtils.reverseUint32(\\n uint32(preimage.extractTxIndexLeAt(68))\\n );\\n\\n return\\n uint256(keccak256(abi.encodePacked(outpointTxHash, outpointIndex)));\\n }\\n\\n /// @notice Extracts the UTXO key from the given preimage used during\\n /// signing of a non-witness input.\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference.\\n /// @return utxoKey UTXO key that identifies spent input.\\n function extractUtxoKeyFromNonWitnessPreimage(bytes calldata preimage)\\n internal\\n pure\\n returns (uint256 utxoKey)\\n {\\n // The expected structure of the preimage created during signing of a\\n // non-witness input:\\n // - transaction version (4 bytes)\\n // - number of inputs written as compactSize uint (1 byte, 3 bytes,\\n // 5 bytes or 9 bytes)\\n // - for each input\\n // - outpoint (hash and index) (36 bytes)\\n // - unlocking script for the input being signed (variable length)\\n // or `00` for all other inputs (1 byte)\\n // - input sequence (4 bytes)\\n // - number of outputs written as compactSize uint (1 byte, 3 bytes,\\n // 5 bytes or 9 bytes)\\n // - outputs (variable length)\\n // - transaction locktime (4 bytes)\\n // - sighash type (4 bytes)\\n\\n // See example for reference:\\n // https://en.bitcoin.it/wiki/OP_CHECKSIG#Code_samples_and_raw_dumps.\\n\\n // The input data begins at the constant offset of 4 (the first 4 bytes\\n // are for the transaction version).\\n (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = preimage\\n .parseVarIntAt(4);\\n\\n // To determine the first input starting index, we must jump 4 bytes\\n // over the transaction version length and the compactSize uint which\\n // prepends the input vector. One byte must be added because\\n // `BtcUtils.parseVarInt` does not include compactSize uint tag in the\\n // 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 = 4 + 1 + inputsCompactSizeUintLength;\\n\\n for (uint256 i = 0; i < inputsCount; i++) {\\n uint256 inputLength = preimage.determineInputLengthAt(\\n inputStartingIndex\\n );\\n\\n (, uint256 scriptSigLength) = preimage.extractScriptSigLenAt(\\n inputStartingIndex\\n );\\n\\n if (scriptSigLength > 0) {\\n // The input this preimage was generated for was found.\\n // All the other inputs in the preimage are marked with a null\\n // scriptSig (\\\"00\\\") which has length of 1.\\n bytes32 outpointTxHash = preimage.extractInputTxIdLeAt(\\n inputStartingIndex\\n );\\n uint32 outpointIndex = BTCUtils.reverseUint32(\\n uint32(preimage.extractTxIndexLeAt(inputStartingIndex))\\n );\\n\\n utxoKey = uint256(\\n keccak256(abi.encodePacked(outpointTxHash, outpointIndex))\\n );\\n\\n break;\\n }\\n\\n inputStartingIndex += inputLength;\\n }\\n\\n return utxoKey;\\n }\\n\\n /// @notice Extracts the sighash type from the given preimage.\\n /// @param preimage Serialized subset of the transaction. See BIP-143 for\\n /// reference.\\n /// @dev Sighash type is stored as the last 4 bytes in the preimage (little\\n /// endian).\\n /// @return sighashType Sighash type as a 32-bit integer.\\n function extractSighashType(bytes calldata preimage)\\n internal\\n pure\\n returns (uint32 sighashType)\\n {\\n bytes4 sighashTypeBytes = preimage.slice4(preimage.length - 4);\\n uint32 sighashTypeLE = uint32(sighashTypeBytes);\\n return sighashTypeLE.reverseUint32();\\n }\\n}\\n\",\"keccak256\":\"0x5b976a45cbee8e5d8e65fc748820e36c692d8fb26a8532319a87e6a614733c58\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Heartbeat.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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).\\n///\\n/// The message being signed by the wallet when executing the heartbeat\\n/// protocol should be Bitcoin's hash256 (double SHA-256) of the heartbeat\\n/// message:\\n/// heartbeat_sighash = hash256(heartbeat_message)\\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\",\"keccak256\":\"0x2f3ad70beadc4dfb6414064fd7b3621b1edcd2713f186853b4a5fd36fb4502ba\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/IRelay.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\n/// @title Interface for the Bitcoin relay\\n/// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay\\n/// provides the difficulty of the previous and current epoch. One\\n/// difficulty epoch spans 2016 blocks.\\ninterface IRelay {\\n /// @notice Returns the difficulty of the current epoch.\\n function getCurrentEpochDifficulty() external view returns (uint256);\\n\\n /// @notice Returns the difficulty of the previous epoch.\\n function getPrevEpochDifficulty() external view returns (uint256);\\n}\\n\",\"keccak256\":\"0xf70c723fc0a1824d92061b5dc76c65c38c22eff6b18ef6a2057f511183ce3c5b\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/MovingFunds.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Redemption.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\n/// @title Moving Bridge wallet funds\\n/// @notice The library handles the logic for moving Bitcoin between Bridge\\n/// wallets.\\n/// @dev A wallet that failed a heartbeat, did not process requested redemption\\n/// on time, or qualifies to be closed, begins the procedure of moving\\n/// funds to other wallets in the Bridge. The wallet needs to commit to\\n/// which other Live wallets it is moving the funds to and then, provide an\\n/// SPV proof of moving funds to the previously committed wallets.\\n/// Once the proof is submitted, all target wallets are supposed to\\n/// sweep the received UTXOs with their own main UTXOs in order to\\n/// update their BTC balances.\\nlibrary MovingFunds {\\n using BridgeState for BridgeState.Storage;\\n using Wallets for BridgeState.Storage;\\n using BitcoinTx for BridgeState.Storage;\\n\\n using BTCUtils for bytes;\\n using BytesLib for bytes;\\n\\n /// @notice Represents temporary information needed during the processing\\n /// of the moving funds Bitcoin transaction outputs. This structure\\n /// is an internal one and should not be exported outside of the\\n /// moving funds transaction processing code.\\n /// @dev Allows to mitigate \\\"stack too deep\\\" errors on EVM.\\n struct MovingFundsTxOutputsProcessingInfo {\\n // 32-byte hash of the moving funds Bitcoin transaction.\\n bytes32 movingFundsTxHash;\\n // Output vector of the moving funds Bitcoin transaction. It is\\n // assumed the vector's structure is valid so it must be validated\\n // using e.g. `BTCUtils.validateVout` function before being used\\n // during the processing. The validation is usually done as part\\n // of the `BitcoinTx.validateProof` call that checks the SPV proof.\\n bytes movingFundsTxOutputVector;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\n\\n /// @notice Represents moved funds sweep request state.\\n enum MovedFundsSweepRequestState {\\n /// @dev The request is unknown to the Bridge.\\n Unknown,\\n /// @dev Request is pending and can become either processed or timed out.\\n Pending,\\n /// @dev Request was processed by the target wallet.\\n Processed,\\n /// @dev Request was not processed in the given time window and\\n /// the timeout was reported.\\n TimedOut\\n }\\n\\n /// @notice Represents a moved funds sweep request. The request is\\n /// registered in `submitMovingFundsProof` where we know funds\\n /// have been moved to the target wallet and the only step left is\\n /// to have the target wallet sweep them.\\n struct MovedFundsSweepRequest {\\n // 20-byte public key hash of the wallet supposed to sweep the UTXO\\n // representing the received funds with their own main UTXO\\n bytes20 walletPubKeyHash;\\n // Value of the received funds.\\n uint64 value;\\n // UNIX timestamp the request was created at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 createdAt;\\n // The current state of the request.\\n MovedFundsSweepRequestState state;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event MovingFundsCommitmentSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes20[] targetWallets,\\n address submitter\\n );\\n\\n event MovingFundsTimeoutReset(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash\\n );\\n\\n event MovingFundsTimedOut(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsBelowDustReported(bytes20 indexed walletPubKeyHash);\\n\\n event MovedFundsSwept(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sweepTxHash\\n );\\n\\n event MovedFundsSweepTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex\\n );\\n\\n /// @notice Submits the moving funds target wallets commitment.\\n /// Once all requirements are met, that function registers the\\n /// target wallets commitment and opens the way for moving funds\\n /// proof submission.\\n /// @param walletPubKeyHash 20-byte public key hash of the source wallet.\\n /// @param walletMainUtxo Data of the source wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @param walletMembersIDs Identifiers of the source wallet signing group\\n /// members.\\n /// @param walletMemberIndex Position of the caller in the source wallet\\n /// signing group members list.\\n /// @param targetWallets List of 20-byte public key hashes of the target\\n /// wallets that the source wallet commits to move the funds to.\\n /// @dev Requirements:\\n /// - The source wallet must be in the MovingFunds state,\\n /// - The source wallet must not have pending redemption requests,\\n /// - The source wallet must not have pending moved funds sweep requests,\\n /// - The source wallet must not have submitted its commitment already,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given source wallet in the ECDSA registry. Those IDs are\\n /// not directly stored in the contract for gas efficiency purposes\\n /// but they can be read from appropriate `DkgResultSubmitted`\\n /// and `DkgResultApproved` events,\\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],\\n /// - The caller must be the member of the source wallet signing group\\n /// at the position indicated by `walletMemberIndex` parameter,\\n /// - The `walletMainUtxo` components must point to the recent main\\n /// UTXO of the source wallet, as currently known on the Ethereum\\n /// chain,\\n /// - Source wallet BTC balance must be greater than zero,\\n /// - At least one Live wallet must exist in the system,\\n /// - Submitted target wallets count must match the expected count\\n /// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`\\n /// where `N > 0`,\\n /// - Each target wallet must be not equal to the source wallet,\\n /// - Each target wallet must follow the expected order i.e. all\\n /// target wallets 20-byte public key hashes represented as numbers\\n /// must form a strictly increasing sequence without duplicates,\\n /// - Each target wallet must be in Live state.\\n function submitMovingFundsCommitment(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo,\\n uint32[] calldata walletMembersIDs,\\n uint256 walletMemberIndex,\\n bytes20[] calldata targetWallets\\n ) external {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.MovingFunds,\\n \\\"Source wallet must be in MovingFunds state\\\"\\n );\\n\\n require(\\n wallet.pendingRedemptionsValue == 0,\\n \\\"Source wallet must handle all pending redemptions first\\\"\\n );\\n\\n require(\\n wallet.pendingMovedFundsSweepRequestsCount == 0,\\n \\\"Source wallet must handle all pending moved funds sweep requests first\\\"\\n );\\n\\n require(\\n wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),\\n \\\"Target wallets commitment already submitted\\\"\\n );\\n\\n require(\\n self.ecdsaWalletRegistry.isWalletMember(\\n wallet.ecdsaWalletID,\\n walletMembersIDs,\\n msg.sender,\\n walletMemberIndex\\n ),\\n \\\"Caller is not a member of the source wallet\\\"\\n );\\n\\n uint64 walletBtcBalance = self.getWalletBtcBalance(\\n walletPubKeyHash,\\n walletMainUtxo\\n );\\n\\n require(walletBtcBalance > 0, \\\"Wallet BTC balance is zero\\\");\\n\\n uint256 expectedTargetWalletsCount = Math.min(\\n self.liveWalletsCount,\\n Math.ceilDiv(walletBtcBalance, self.walletMaxBtcTransfer)\\n );\\n\\n // This requirement fails only when `liveWalletsCount` is zero. In\\n // that case, the system cannot accept the commitment and must provide\\n // new wallets first. However, the wallet supposed to submit the\\n // commitment can keep resetting the moving funds timeout until then.\\n require(expectedTargetWalletsCount > 0, \\\"No target wallets available\\\");\\n\\n require(\\n targetWallets.length == expectedTargetWalletsCount,\\n \\\"Submitted target wallets count is other than expected\\\"\\n );\\n\\n uint160 lastProcessedTargetWallet = 0;\\n\\n for (uint256 i = 0; i < targetWallets.length; i++) {\\n bytes20 targetWallet = targetWallets[i];\\n\\n require(\\n targetWallet != walletPubKeyHash,\\n \\\"Submitted target wallet cannot be equal to the source wallet\\\"\\n );\\n\\n require(\\n uint160(targetWallet) > lastProcessedTargetWallet,\\n \\\"Submitted target wallet breaks the expected order\\\"\\n );\\n\\n require(\\n self.registeredWallets[targetWallet].state ==\\n Wallets.WalletState.Live,\\n \\\"Submitted target wallet must be in Live state\\\"\\n );\\n\\n lastProcessedTargetWallet = uint160(targetWallet);\\n }\\n\\n wallet.movingFundsTargetWalletsCommitmentHash = keccak256(\\n abi.encodePacked(targetWallets)\\n );\\n\\n emit MovingFundsCommitmentSubmitted(\\n walletPubKeyHash,\\n targetWallets,\\n msg.sender\\n );\\n }\\n\\n /// @notice Resets the moving funds timeout for the given wallet if the\\n /// target wallet commitment cannot be submitted due to a lack\\n /// of live wallets in the system.\\n /// @param walletPubKeyHash 20-byte public key hash of the moving funds wallet\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The target wallets commitment must not be already submitted for\\n /// the given moving funds wallet,\\n /// - Live wallets count must be zero,\\n /// - The moving funds timeout reset delay must be elapsed.\\n function resetMovingFundsTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) external {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n // If the moving funds wallet already submitted their target wallets\\n // commitment, there is no point to reset the timeout since the\\n // wallet can make the BTC transaction and submit the proof.\\n require(\\n wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),\\n \\\"Target wallets commitment already submitted\\\"\\n );\\n\\n require(self.liveWalletsCount == 0, \\\"Live wallets count must be zero\\\");\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >\\n wallet.movingFundsRequestedAt +\\n self.movingFundsTimeoutResetDelay,\\n \\\"Moving funds timeout cannot be reset yet\\\"\\n );\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.movingFundsRequestedAt = uint32(block.timestamp);\\n\\n emit MovingFundsTimeoutReset(walletPubKeyHash);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moving funds transaction\\n /// and to make the necessary state changes. Moving funds is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the moving funds transaction structure\\n /// by checking if it actually spends the main UTXO of the declared\\n /// wallet and locks the value on the pre-committed target wallets\\n /// using a reasonable transaction fee. If all preconditions are\\n /// met, this functions closes the source wallet.\\n ///\\n /// It is possible to prove the given moving funds transaction only\\n /// one time.\\n /// @param movingFundsTx Bitcoin moving funds transaction data.\\n /// @param movingFundsProof Bitcoin moving funds proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet\\n /// which performed the moving funds transaction.\\n /// @dev Requirements:\\n /// - `movingFundsTx` 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 `movingFundsTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs corresponding to the\\n /// pre-committed target wallets. Outputs must be ordered in the\\n /// same way as their corresponding target wallets are ordered\\n /// within the target wallets commitment,\\n /// - `movingFundsProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input,\\n /// - The wallet that `walletPubKeyHash` points to must be in the\\n /// MovingFunds state,\\n /// - The target wallets commitment must be submitted by the wallet\\n /// that `walletPubKeyHash` points to,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movingFundsTxMaxTotalFee` governable parameter.\\n function submitMovingFundsProof(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata movingFundsTx,\\n BitcoinTx.Proof calldata movingFundsProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external {\\n // Wallet state is validated in `notifyWalletFundsMoved`.\\n\\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 movingFundsTxHash = self.validateProof(\\n movingFundsTx,\\n movingFundsProof\\n );\\n\\n // Assert that main UTXO for passed wallet exists in storage.\\n bytes32 mainUtxoHash = self\\n .registeredWallets[walletPubKeyHash]\\n .mainUtxoHash;\\n require(mainUtxoHash != bytes32(0), \\\"No main UTXO for given wallet\\\");\\n\\n // Assert that passed main UTXO parameter is the same as in storage and\\n // can be used for further processing.\\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\\n // Process the moving funds transaction input. Specifically, check if\\n // it refers to the expected wallet's main UTXO.\\n OutboundTx.processWalletOutboundTxInput(\\n self,\\n movingFundsTx.inputVector,\\n mainUtxo\\n );\\n\\n (\\n bytes32 targetWalletsHash,\\n uint256 outputsTotalValue\\n ) = processMovingFundsTxOutputs(\\n self,\\n MovingFundsTxOutputsProcessingInfo(\\n movingFundsTxHash,\\n movingFundsTx.outputVector\\n )\\n );\\n\\n require(\\n mainUtxo.txOutputValue - outputsTotalValue <=\\n self.movingFundsTxMaxTotalFee,\\n \\\"Transaction fee is too high\\\"\\n );\\n\\n self.notifyWalletFundsMoved(walletPubKeyHash, targetWalletsHash);\\n // slither-disable-next-line reentrancy-events\\n emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);\\n }\\n\\n /// @notice Processes the moving funds Bitcoin transaction output vector\\n /// and extracts information required for further processing.\\n /// @param processInfo Processing info containing the moving funds tx\\n /// hash and output vector.\\n /// @return targetWalletsHash keccak256 hash over the list of actual\\n /// target wallets used in the transaction.\\n /// @return outputsTotalValue Sum of all outputs values.\\n /// @dev Requirements:\\n /// - The `movingFundsTxOutputVector` must be parseable, i.e. must\\n /// be validated by the caller as stated in their parameter doc,\\n /// - Each output must refer to a 20-byte public key hash,\\n /// - The total outputs value must be evenly divided over all outputs.\\n function processMovingFundsTxOutputs(\\n BridgeState.Storage storage self,\\n MovingFundsTxOutputsProcessingInfo memory processInfo\\n ) internal returns (bytes32 targetWalletsHash, uint256 outputsTotalValue) {\\n // Determining the total number of Bitcoin transaction outputs in\\n // the same way as for number of inputs. See `BitcoinTx.outputVector`\\n // docs for more details.\\n (\\n uint256 outputsCompactSizeUintLength,\\n uint256 outputsCount\\n ) = processInfo.movingFundsTxOutputVector.parseVarInt();\\n\\n // To determine the first output starting index, we must jump over\\n // the compactSize uint which prepends the output 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 outputStartingIndex = 1 + outputsCompactSizeUintLength;\\n\\n bytes20[] memory targetWallets = new bytes20[](outputsCount);\\n uint64[] memory outputsValues = new uint64[](outputsCount);\\n\\n // Outputs processing loop. Note that the `outputIndex` must be\\n // `uint32` to build proper `movedFundsSweepRequests` keys.\\n for (\\n uint32 outputIndex = 0;\\n outputIndex < outputsCount;\\n outputIndex++\\n ) {\\n uint256 outputLength = processInfo\\n .movingFundsTxOutputVector\\n .determineOutputLengthAt(outputStartingIndex);\\n\\n bytes memory output = processInfo.movingFundsTxOutputVector.slice(\\n outputStartingIndex,\\n outputLength\\n );\\n\\n bytes20 targetWalletPubKeyHash = self.extractPubKeyHash(output);\\n\\n // Add the wallet public key hash to the list that will be used\\n // to build the result list hash. There is no need to check if\\n // given output is a change here because the actual target wallet\\n // list must be exactly the same as the pre-committed target wallet\\n // list which is guaranteed to be valid.\\n targetWallets[outputIndex] = targetWalletPubKeyHash;\\n\\n // Extract the value from given output.\\n outputsValues[outputIndex] = output.extractValue();\\n outputsTotalValue += outputsValues[outputIndex];\\n\\n // Register a moved funds sweep request that must be handled\\n // by the target wallet. The target wallet must sweep the\\n // received funds with their own main UTXO in order to update\\n // their BTC balance. Worth noting there is no need to check\\n // if the sweep request already exists in the system because\\n // the moving funds wallet is moved to the Closing state after\\n // submitting the moving funds proof so there is no possibility\\n // to submit the proof again and register the sweep request twice.\\n self.movedFundsSweepRequests[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n processInfo.movingFundsTxHash,\\n outputIndex\\n )\\n )\\n )\\n ] = MovedFundsSweepRequest(\\n targetWalletPubKeyHash,\\n outputsValues[outputIndex],\\n /* solhint-disable-next-line not-rely-on-time */\\n uint32(block.timestamp),\\n MovedFundsSweepRequestState.Pending\\n );\\n // We added a new moved funds sweep request for the target wallet\\n // so we must increment their request counter.\\n self\\n .registeredWallets[targetWalletPubKeyHash]\\n .pendingMovedFundsSweepRequestsCount++;\\n\\n // Make the `outputStartingIndex` pointing to the next output by\\n // increasing it by current output's length.\\n outputStartingIndex += outputLength;\\n }\\n\\n // Compute the indivisible remainder that remains after dividing the\\n // outputs total value over all outputs evenly.\\n uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;\\n // Compute the minimum allowed output value by dividing the outputs\\n // total value (reduced by the remainder) by the number of outputs.\\n uint256 minOutputValue = (outputsTotalValue -\\n outputsTotalValueRemainder) / outputsCount;\\n // Maximum possible value is the minimum value with the remainder included.\\n uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;\\n\\n for (uint256 i = 0; i < outputsCount; i++) {\\n require(\\n minOutputValue <= outputsValues[i] &&\\n outputsValues[i] <= maxOutputValue,\\n \\\"Transaction amount is not distributed evenly\\\"\\n );\\n }\\n\\n targetWalletsHash = keccak256(abi.encodePacked(targetWallets));\\n\\n return (targetWalletsHash, outputsTotalValue);\\n }\\n\\n /// @notice Notifies about a timed out moving funds process. Terminates\\n /// the wallet and slashes signing group members as a result.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The moving funds timeout must be actually exceeded,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovingFundsTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n // Wallet state is validated in `notifyWalletMovingFundsTimeout`.\\n\\n uint32 movingFundsRequestedAt = self\\n .registeredWallets[walletPubKeyHash]\\n .movingFundsRequestedAt;\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp > movingFundsRequestedAt + self.movingFundsTimeout,\\n \\\"Moving funds has not timed out yet\\\"\\n );\\n\\n self.notifyWalletMovingFundsTimeout(walletPubKeyHash, walletMembersIDs);\\n\\n // slither-disable-next-line reentrancy-events\\n emit MovingFundsTimedOut(walletPubKeyHash);\\n }\\n\\n /// @notice Notifies about a moving funds wallet whose BTC balance is\\n /// below the moving funds dust threshold. Ends the moving funds\\n /// process and begins wallet closing immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known\\n /// on the Ethereum chain.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain.\\n /// If the wallet has no main UTXO, this parameter can be empty as it\\n /// is ignored,\\n /// - The wallet BTC balance must be below the moving funds threshold.\\n function notifyMovingFundsBelowDust(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external {\\n // Wallet state is validated in `notifyWalletMovingFundsBelowDust`.\\n\\n uint64 walletBtcBalance = self.getWalletBtcBalance(\\n walletPubKeyHash,\\n mainUtxo\\n );\\n\\n require(\\n walletBtcBalance < self.movingFundsDustThreshold,\\n \\\"Wallet BTC balance must be below the moving funds dust threshold\\\"\\n );\\n\\n self.notifyWalletMovingFundsBelowDust(walletPubKeyHash);\\n\\n // slither-disable-next-line reentrancy-events\\n emit MovingFundsBelowDustReported(walletPubKeyHash);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moved funds sweep\\n /// transaction and to make the necessary state changes. Moved\\n /// funds sweep is only accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the sweep transaction structure by\\n /// checking if it actually spends the moved funds UTXO and the\\n /// sweeping wallet's main UTXO (optionally), and if it locks the\\n /// value on the sweeping wallet's 20-byte public key hash using a\\n /// reasonable transaction fee. If all preconditions are\\n /// met, this function updates the sweeping wallet main UTXO, thus\\n /// their BTC balance.\\n ///\\n /// It is possible to prove the given sweep transaction only\\n /// one time.\\n /// @param sweepTx Bitcoin sweep funds transaction data.\\n /// @param sweepProof Bitcoin sweep funds proof data.\\n /// @param mainUtxo Data of the sweeping wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\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\\n /// the first input pointing to a wallet's sweep Pending request and,\\n /// optionally, the second input pointing to the wallet's main UTXO,\\n /// if the sweeping wallet has a main UTXO set. There should be only\\n /// one output locking funds on the sweeping wallet 20-byte public\\n /// key hash,\\n /// - `sweepProof` components must match the expected structure.\\n /// See `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 sweeping wallet, as currently known on the Ethereum chain.\\n /// If there is no main UTXO, this parameter is ignored,\\n /// - The sweeping wallet must be in the Live or MovingFunds state,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movedFundsSweepTxMaxTotalFee` governable parameter.\\n function submitMovedFundsSweepProof(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata sweepTx,\\n BitcoinTx.Proof calldata sweepProof,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external {\\n // Wallet state validation is performed in the\\n // `resolveMovedFundsSweepingWallet` function.\\n\\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 (\\n bytes20 walletPubKeyHash,\\n uint64 sweepTxOutputValue\\n ) = processMovedFundsSweepTxOutput(self, sweepTx.outputVector);\\n\\n (\\n Wallets.Wallet storage wallet,\\n BitcoinTx.UTXO memory resolvedMainUtxo\\n ) = resolveMovedFundsSweepingWallet(self, walletPubKeyHash, mainUtxo);\\n\\n uint256 sweepTxInputsTotalValue = processMovedFundsSweepTxInputs(\\n self,\\n sweepTx.inputVector,\\n resolvedMainUtxo,\\n walletPubKeyHash\\n );\\n\\n require(\\n sweepTxInputsTotalValue - sweepTxOutputValue <=\\n self.movedFundsSweepTxMaxTotalFee,\\n \\\"Transaction fee is too high\\\"\\n );\\n\\n // Use the sweep transaction output as the new sweeping wallet's main UTXO.\\n // Transaction output index is always 0 as sweep transaction always\\n // 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 MovedFundsSwept(walletPubKeyHash, sweepTxHash);\\n }\\n\\n /// @notice Processes the Bitcoin moved funds sweep transaction output vector\\n /// by 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 moved funds sweep transaction output\\n /// 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 moved funds sweep transaction output value.\\n /// @dev Requirements:\\n /// - Output vector must contain only one output,\\n /// - The single output must be of P2PKH or P2WPKH type and lock the\\n /// funds on a 20-byte public key hash.\\n function processMovedFundsSweepTxOutput(\\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 performed as\\n // part of the `BitcoinTx.validateProof` call.\\n // See `BitcoinTx.outputVector` docs for more details.\\n (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();\\n require(\\n outputsCount == 1,\\n \\\"Moved funds 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 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 resolveMovedFundsSweepingWallet(\\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 moved funds sweep transaction input vector.\\n /// It extracts the first input and tries to match it with one of\\n /// the moved funds sweep requests targeting the sweeping wallet.\\n /// If the sweep request is an existing Pending request, this\\n /// function marks it as Processed. If the sweeping wallet has a\\n /// main UTXO, this function extracts the second input, makes sure\\n /// it refers to the wallet main UTXO, and marks that main UTXO as\\n /// correctly spent.\\n /// @param sweepTxInputVector Bitcoin moved funds 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 sweeping 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 /// @param walletPubKeyHash 20-byte public key hash of the sweeping wallet.\\n /// @return inputsTotalValue Total inputs value sum.\\n /// @dev Requirements:\\n /// - The input vector must consist of one mandatory and one optional\\n /// input,\\n /// - The mandatory input must be the first input in the vector,\\n /// - The mandatory input must point to a Pending moved funds sweep\\n /// request that is targeted to the sweeping wallet,\\n /// - The optional output must be the second input in the vector,\\n /// - The optional input is required if the sweeping wallet has a\\n /// main UTXO (i.e. the `mainUtxo` is not zeroed). In that case,\\n /// that input must point the the sweeping wallet main UTXO.\\n function processMovedFundsSweepTxInputs(\\n BridgeState.Storage storage self,\\n bytes memory sweepTxInputVector,\\n BitcoinTx.UTXO memory mainUtxo,\\n bytes20 walletPubKeyHash\\n ) internal returns (uint256 inputsTotalValue) {\\n // To determine the total number of Bitcoin transaction inputs,\\n // we need to parse the compactSize uint (VarInt) the input vector is\\n // prepended by. That compactSize uint encodes the number of vector\\n // elements using the 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 `validateVin` validation performed as\\n // part of the `BitcoinTx.validateProof` call.\\n // See `BitcoinTx.inputVector` docs for 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 // We always expect the first input to be the swept UTXO. Additionally,\\n // if the sweeping wallet has a main UTXO, that main UTXO should be\\n // pointed by the second input.\\n require(\\n inputsCount == (mainUtxo.txHash != bytes32(0) ? 2 : 1),\\n \\\"Moved funds sweep transaction must have a proper inputs count\\\"\\n );\\n\\n // Parse the first input and extract its outpoint tx hash and index.\\n (\\n bytes32 firstInputOutpointTxHash,\\n uint32 firstInputOutpointIndex,\\n uint256 firstInputLength\\n ) = parseMovedFundsSweepTxInputAt(\\n sweepTxInputVector,\\n inputStartingIndex\\n );\\n\\n // Build the request key and fetch the corresponding moved funds sweep\\n // request from contract storage.\\n MovedFundsSweepRequest storage sweepRequest = self\\n .movedFundsSweepRequests[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n firstInputOutpointTxHash,\\n firstInputOutpointIndex\\n )\\n )\\n )\\n ];\\n\\n require(\\n sweepRequest.state == MovedFundsSweepRequestState.Pending,\\n \\\"Sweep request must be in Pending state\\\"\\n );\\n // We must check if the wallet extracted from the moved funds sweep\\n // transaction output is truly the owner of the sweep request connected\\n // with the swept UTXO. This is needed to prevent a case when a wallet\\n // handles its own sweep request but locks the funds on another\\n // wallet public key hash.\\n require(\\n sweepRequest.walletPubKeyHash == walletPubKeyHash,\\n \\\"Sweep request belongs to another wallet\\\"\\n );\\n // If the validation passed, the sweep request must be marked as\\n // processed and its value should be counted into the total inputs\\n // value sum.\\n sweepRequest.state = MovedFundsSweepRequestState.Processed;\\n inputsTotalValue += sweepRequest.value;\\n\\n self\\n .registeredWallets[walletPubKeyHash]\\n .pendingMovedFundsSweepRequestsCount--;\\n\\n // If the main UTXO for the sweeping wallet exists, it must be processed.\\n if (mainUtxo.txHash != bytes32(0)) {\\n // The second input is supposed to point to that sweeping wallet\\n // main UTXO. We need to parse that input.\\n (\\n bytes32 secondInputOutpointTxHash,\\n uint32 secondInputOutpointIndex,\\n\\n ) = parseMovedFundsSweepTxInputAt(\\n sweepTxInputVector,\\n inputStartingIndex + firstInputLength\\n );\\n // Make sure the second input refers to the sweeping wallet main UTXO.\\n require(\\n mainUtxo.txHash == secondInputOutpointTxHash &&\\n mainUtxo.txOutputIndex == secondInputOutpointIndex,\\n \\\"Second input must point to the wallet's main UTXO\\\"\\n );\\n\\n // If the validation passed, count the main UTXO value into the\\n // total inputs value sum.\\n inputsTotalValue += mainUtxo.txOutputValue;\\n\\n // Main UTXO used as an input, mark it as spent. This is needed\\n // to defend against fraud challenges referring to this main UTXO.\\n self.spentMainUTXOs[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n secondInputOutpointTxHash,\\n secondInputOutpointIndex\\n )\\n )\\n )\\n ] = true;\\n }\\n\\n return inputsTotalValue;\\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 parseMovedFundsSweepTxInputAt(\\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 Notifies about a timed out moved funds sweep process. If the\\n /// wallet is not terminated yet, that function terminates\\n /// the wallet and slashes signing group members as a result.\\n /// Marks the given sweep request as TimedOut.\\n /// @param movingFundsTxHash 32-byte hash of the moving funds transaction\\n /// that caused the sweep request to be created.\\n /// @param movingFundsTxOutputIndex Index of the moving funds transaction\\n /// output that is subject of the sweep request.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The moved funds sweep request must be in the Pending state,\\n /// - The moved funds sweep timeout must be actually exceeded,\\n /// - The wallet must be either in the Live or MovingFunds or\\n /// Terminated state,,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovedFundsSweepTimeout(\\n BridgeState.Storage storage self,\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n // Wallet state is validated in `notifyWalletMovedFundsSweepTimeout`.\\n\\n MovedFundsSweepRequest storage sweepRequest = self\\n .movedFundsSweepRequests[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n movingFundsTxHash,\\n movingFundsTxOutputIndex\\n )\\n )\\n )\\n ];\\n\\n require(\\n sweepRequest.state == MovedFundsSweepRequestState.Pending,\\n \\\"Sweep request must be in Pending state\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >\\n sweepRequest.createdAt + self.movedFundsSweepTimeout,\\n \\\"Sweep request has not timed out yet\\\"\\n );\\n\\n bytes20 walletPubKeyHash = sweepRequest.walletPubKeyHash;\\n\\n self.notifyWalletMovedFundsSweepTimeout(\\n walletPubKeyHash,\\n walletMembersIDs\\n );\\n\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n sweepRequest.state = MovedFundsSweepRequestState.TimedOut;\\n wallet.pendingMovedFundsSweepRequestsCount--;\\n\\n // slither-disable-next-line reentrancy-events\\n emit MovedFundsSweepTimedOut(\\n walletPubKeyHash,\\n movingFundsTxHash,\\n movingFundsTxOutputIndex\\n );\\n }\\n}\\n\",\"keccak256\":\"0xce1afc1875d364e64cfa088558ee166a441e55ef6727f38002934654ac14b25d\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Redemption.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\nimport \\\"../bank/Bank.sol\\\";\\n\\n/// @notice Aggregates functions common to the redemption transaction proof\\n/// validation and to the moving funds transaction proof validation.\\nlibrary OutboundTx {\\n using BTCUtils for bytes;\\n\\n /// @notice Checks whether an outbound Bitcoin transaction performed from\\n /// the given wallet has an input vector that contains a single\\n /// input referring to the wallet's main UTXO. Marks that main UTXO\\n /// as correctly spent if the validation succeeds. Reverts otherwise.\\n /// There are two outbound transactions from a wallet possible: a\\n /// redemption transaction or a moving funds to another wallet\\n /// transaction.\\n /// @param walletOutboundTxInputVector Bitcoin outbound transaction's input\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVin` function\\n /// before it is passed here.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n function processWalletOutboundTxInput(\\n BridgeState.Storage storage self,\\n bytes memory walletOutboundTxInputVector,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) internal {\\n // Assert that the single outbound transaction input actually\\n // refers to the wallet's main UTXO.\\n (\\n bytes32 outpointTxHash,\\n uint32 outpointIndex\\n ) = parseWalletOutboundTxInput(walletOutboundTxInputVector);\\n require(\\n mainUtxo.txHash == outpointTxHash &&\\n mainUtxo.txOutputIndex == outpointIndex,\\n \\\"Outbound transaction input must point to the wallet's main UTXO\\\"\\n );\\n\\n // Main UTXO used as an input, mark it as spent.\\n self.spentMainUTXOs[\\n uint256(\\n keccak256(\\n abi.encodePacked(mainUtxo.txHash, mainUtxo.txOutputIndex)\\n )\\n )\\n ] = true;\\n }\\n\\n /// @notice Parses the input vector of an outbound Bitcoin transaction\\n /// performed from the given wallet. It extracts the single input\\n /// then the transaction hash and output index from its outpoint.\\n /// There are two outbound transactions from a wallet possible: a\\n /// redemption transaction or a moving funds to another wallet\\n /// transaction.\\n /// @param walletOutboundTxInputVector Bitcoin outbound transaction input\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVin` function\\n /// before it is passed here.\\n /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is\\n /// pointed in the input's outpoint.\\n /// @return outpointIndex 4-byte index of the Bitcoin transaction output\\n /// which is pointed in the input's outpoint.\\n function parseWalletOutboundTxInput(\\n bytes memory walletOutboundTxInputVector\\n ) internal pure returns (bytes32 outpointTxHash, uint32 outpointIndex) {\\n // To determine the total number of Bitcoin transaction inputs,\\n // we need to parse the compactSize uint (VarInt) the input vector is\\n // prepended by. That compactSize uint encodes the number of vector\\n // elements using the 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 `validateVin` validation.\\n // See `BitcoinTx.inputVector` docs for more details.\\n (, uint256 inputsCount) = walletOutboundTxInputVector.parseVarInt();\\n require(\\n inputsCount == 1,\\n \\\"Outbound transaction must have a single input\\\"\\n );\\n\\n bytes memory input = walletOutboundTxInputVector.extractInputAtIndex(0);\\n\\n outpointTxHash = input.extractInputTxIdLE();\\n\\n outpointIndex = BTCUtils.reverseUint32(\\n uint32(input.extractTxIndexLE())\\n );\\n\\n // There is only one input in the transaction. Input has an outpoint\\n // field that is a reference to the transaction being spent (see\\n // `BitcoinTx` docs). The outpoint contains the hash of the transaction\\n // to spend (`outpointTxHash`) and the index of the specific output\\n // from that transaction (`outpointIndex`).\\n return (outpointTxHash, outpointIndex);\\n }\\n}\\n\\n/// @title Bridge redemption\\n/// @notice The library handles the logic for redeeming Bitcoin balances from\\n/// the Bridge.\\n/// @dev To initiate a redemption, a user with a Bank balance supplies\\n/// a Bitcoin address. Then, the system calculates the redemption fee, and\\n/// releases balance to the provided Bitcoin address. Just like in case of\\n/// sweeps of revealed deposits, redemption requests are processed in\\n/// batches and require SPV proof to be submitted to the Bridge.\\nlibrary Redemption {\\n using BridgeState for BridgeState.Storage;\\n using Wallets for BridgeState.Storage;\\n using BitcoinTx for BridgeState.Storage;\\n\\n using BTCUtils for bytes;\\n using BytesLib for bytes;\\n\\n /// @notice Represents a redemption request.\\n struct RedemptionRequest {\\n // ETH address of the redeemer who created the request.\\n address redeemer;\\n // Requested TBTC amount in satoshi.\\n uint64 requestedAmount;\\n // Treasury TBTC fee in satoshi at the moment of request creation.\\n uint64 treasuryFee;\\n // Transaction maximum BTC fee in satoshi at the moment of request\\n // creation.\\n uint64 txMaxFee;\\n // UNIX timestamp the request was created at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 requestedAt;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n /// @notice Represents an outcome of the redemption Bitcoin transaction\\n /// outputs processing.\\n struct RedemptionTxOutputsInfo {\\n // Sum of all outputs values i.e. all redemptions and change value,\\n // if present.\\n uint256 outputsTotalValue;\\n // Total TBTC value in satoshi that should be burned by the Bridge.\\n // It includes the total amount of all BTC redeemed in the transaction\\n // and the fee paid to BTC miners for the redemption transaction.\\n uint64 totalBurnableValue;\\n // Total TBTC value in satoshi that should be transferred to\\n // the treasury. It is a sum of all treasury fees paid by all\\n // redeemers included in the redemption transaction.\\n uint64 totalTreasuryFee;\\n // Index of the change output. The change output becomes\\n // the new main wallet's UTXO.\\n uint32 changeIndex;\\n // Value in satoshi of the change output.\\n uint64 changeValue;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\n\\n /// @notice Represents temporary information needed during the processing of\\n /// the redemption Bitcoin transaction outputs. This structure is an\\n /// internal one and should not be exported outside of the redemption\\n /// transaction processing code.\\n /// @dev Allows to mitigate \\\"stack too deep\\\" errors on EVM.\\n struct RedemptionTxOutputsProcessingInfo {\\n // The first output starting index in the transaction.\\n uint256 outputStartingIndex;\\n // The number of outputs in the transaction.\\n uint256 outputsCount;\\n // P2PKH script for the wallet. Needed to determine the change output.\\n bytes32 walletP2PKHScriptKeccak;\\n // P2WPKH script for the wallet. Needed to determine the change output.\\n bytes32 walletP2WPKHScriptKeccak;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\n\\n event RedemptionRequested(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript,\\n address indexed redeemer,\\n uint64 requestedAmount,\\n uint64 treasuryFee,\\n uint64 txMaxFee\\n );\\n\\n event RedemptionsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 redemptionTxHash\\n );\\n\\n event RedemptionTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript\\n );\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script.\\n /// This function handles the simplest case, where balance owner is\\n /// the redeemer.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed. Balance owner address is stored as\\n /// a redeemer address who will be able co claim back the Bank\\n /// balance if anything goes wrong during the redemption.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to proceed the request,\\n /// - Balance owner must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo,\\n address balanceOwner,\\n bytes calldata redeemerOutputScript,\\n uint64 amount\\n ) external {\\n requestRedemption(\\n self,\\n walletPubKeyHash,\\n mainUtxo,\\n balanceOwner,\\n balanceOwner,\\n redeemerOutputScript,\\n amount\\n );\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script. Used by\\n /// `Bridge.receiveBalanceApproval`. Can handle more complex cases\\n /// where balance owner may be someone else than the redeemer.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @param redemptionData ABI-encoded redemption data:\\n /// [\\n /// address redeemer,\\n /// bytes20 walletPubKeyHash,\\n /// bytes32 mainUtxoTxHash,\\n /// uint32 mainUtxoTxOutputIndex,\\n /// uint64 mainUtxoTxOutputValue,\\n /// bytes redeemerOutputScript\\n /// ]\\n ///\\n /// - redeemer: The Ethereum address of the redeemer who will be able\\n /// to claim Bank balance if anything goes wrong during the redemption.\\n /// In the most basic case, when someone redeems their Bitcoin\\n /// balance from the Bank, `balanceOwner` is the same as `redeemer`.\\n /// However, when a Vault is redeeming part of its balance for some\\n /// redeemer address (for example, someone who has earlier deposited\\n /// into that Vault), `balanceOwner` is the Vault, and `redeemer` is\\n /// the address for which the vault is redeeming its balance to,\\n /// - walletPubKeyHash: The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key),\\n /// - mainUtxoTxHash: Data of the wallet's main UTXO TX hash, as\\n /// currently known on the Ethereum chain,\\n /// - mainUtxoTxOutputIndex: Data of the wallet's main UTXO output\\n /// index, as currently known on Ethereum chain,\\n /// - mainUtxoTxOutputValue: Data of the wallet's main UTXO output\\n /// value, as currently known on Ethereum chain,\\n /// - redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo*` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to proceed the request,\\n /// - Balance owner must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n BridgeState.Storage storage self,\\n address balanceOwner,\\n uint64 amount,\\n bytes calldata redemptionData\\n ) external {\\n (\\n address redeemer,\\n bytes20 walletPubKeyHash,\\n bytes32 mainUtxoTxHash,\\n uint32 mainUtxoTxOutputIndex,\\n uint64 mainUtxoTxOutputValue,\\n bytes memory redeemerOutputScript\\n ) = abi.decode(\\n redemptionData,\\n (address, bytes20, bytes32, uint32, uint64, bytes)\\n );\\n\\n requestRedemption(\\n self,\\n walletPubKeyHash,\\n BitcoinTx.UTXO(\\n mainUtxoTxHash,\\n mainUtxoTxOutputIndex,\\n mainUtxoTxOutputValue\\n ),\\n balanceOwner,\\n redeemer,\\n redeemerOutputScript,\\n amount\\n );\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed.\\n /// @param redeemer The Ethereum address of the redeemer who will be able to\\n /// claim Bank balance if anything goes wrong during the redemption.\\n /// In the most basic case, when someone redeems their Bitcoin\\n /// balance from the Bank, `balanceOwner` is the same as `redeemer`.\\n /// However, when a Vault is redeeming part of its balance for some\\n /// redeemer address (for example, someone who has earlier deposited\\n /// into that Vault), `balanceOwner` is the Vault, and `redeemer` is\\n /// the address for which the vault is redeeming its balance to.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to proceed the request,\\n /// - Balance owner must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO memory mainUtxo,\\n address balanceOwner,\\n address redeemer,\\n bytes memory redeemerOutputScript,\\n uint64 amount\\n ) internal {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n bytes32 mainUtxoHash = wallet.mainUtxoHash;\\n require(\\n mainUtxoHash != bytes32(0),\\n \\\"No main UTXO for the given wallet\\\"\\n );\\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\\n // Validate if redeemer output script is a correct standard type\\n // (P2PKH, P2WPKH, P2SH or P2WSH). This is done by using\\n // `BTCUtils.extractHashAt` on it. Such a function extracts the payload\\n // properly only from standard outputs so if it succeeds, we have a\\n // guarantee the redeemer output script is proper. The underlying way\\n // of validation is the same as in tBTC v1.\\n bytes memory redeemerOutputScriptPayload = redeemerOutputScript\\n .extractHashAt(0, redeemerOutputScript.length);\\n\\n require(\\n redeemerOutputScriptPayload.length > 0,\\n \\\"Redeemer output script must be a standard type\\\"\\n );\\n // Check if the redeemer output script payload does not point to the\\n // wallet public key hash.\\n require(\\n redeemerOutputScriptPayload.length != 20 ||\\n walletPubKeyHash != redeemerOutputScriptPayload.slice20(0),\\n \\\"Redeemer output script must not point to the wallet PKH\\\"\\n );\\n\\n require(\\n amount >= self.redemptionDustThreshold,\\n \\\"Redemption amount too small\\\"\\n );\\n\\n // The redemption key is built on top of the wallet public key hash\\n // and redeemer output script pair. That means there can be only one\\n // request asking for redemption from the given wallet to the given\\n // BTC script at the same time.\\n uint256 redemptionKey = getRedemptionKey(\\n walletPubKeyHash,\\n redeemerOutputScript\\n );\\n\\n // Check if given redemption key is not used by a pending redemption.\\n // There is no need to check for existence in `timedOutRedemptions`\\n // since the wallet's state is changed to other than Live after\\n // first time out is reported so making new requests is not possible.\\n // slither-disable-next-line incorrect-equality\\n require(\\n self.pendingRedemptions[redemptionKey].requestedAt == 0,\\n \\\"There is a pending redemption request from this wallet to the same address\\\"\\n );\\n\\n // No need to check whether `amount - treasuryFee - txMaxFee > 0`\\n // since the `redemptionDustThreshold` should force that condition\\n // to be always true.\\n uint64 treasuryFee = self.redemptionTreasuryFeeDivisor > 0\\n ? amount / self.redemptionTreasuryFeeDivisor\\n : 0;\\n uint64 txMaxFee = self.redemptionTxMaxFee;\\n\\n // The main wallet UTXO's value doesn't include all pending redemptions.\\n // To determine if the requested redemption can be performed by the\\n // wallet we need to subtract the total value of all pending redemptions\\n // from that wallet's main UTXO value. Given that the treasury fee is\\n // not redeemed from the wallet, we are subtracting it.\\n wallet.pendingRedemptionsValue += amount - treasuryFee;\\n require(\\n mainUtxo.txOutputValue >= wallet.pendingRedemptionsValue,\\n \\\"Insufficient wallet funds\\\"\\n );\\n\\n self.pendingRedemptions[redemptionKey] = RedemptionRequest(\\n redeemer,\\n amount,\\n treasuryFee,\\n txMaxFee,\\n /* solhint-disable-next-line not-rely-on-time */\\n uint32(block.timestamp)\\n );\\n\\n // slither-disable-next-line reentrancy-events\\n emit RedemptionRequested(\\n walletPubKeyHash,\\n redeemerOutputScript,\\n redeemer,\\n amount,\\n treasuryFee,\\n txMaxFee\\n );\\n\\n self.bank.transferBalanceFrom(balanceOwner, address(this), amount);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC redemption transaction\\n /// and to make the necessary bookkeeping. Redemption is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function is performing Bank balance updates by burning\\n /// the total redeemed Bitcoin amount from Bridge balance and\\n /// transferring the treasury fee sum to the treasury address.\\n ///\\n /// It is possible to prove the given redemption only one time.\\n /// @param redemptionTx Bitcoin redemption transaction data.\\n /// @param redemptionProof Bitcoin redemption proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @dev Requirements:\\n /// - `redemptionTx` 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 `redemptionTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs handling existing pending\\n /// redemption requests or pointing to reported timed out requests.\\n /// There can be also 1 optional output representing the\\n /// change and pointing back to the 20-byte wallet public key hash.\\n /// The change should be always present if the redeemed value sum\\n /// is lower than the total wallet's BTC balance,\\n /// - `redemptionProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input.\\n /// Other remarks:\\n /// - Putting the change output as the first transaction output can\\n /// save some gas because the output processing loop begins each\\n /// iteration by checking whether the given output is the change\\n /// thus uses some gas for making the comparison. Once the change\\n /// is identified, that check is omitted in further iterations.\\n function submitRedemptionProof(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata redemptionTx,\\n BitcoinTx.Proof calldata redemptionProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external {\\n // Wallet state validation is performed in the `resolveRedeemingWallet`\\n // function.\\n\\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 redemptionTxHash = self.validateProof(\\n redemptionTx,\\n redemptionProof\\n );\\n\\n Wallets.Wallet storage wallet = resolveRedeemingWallet(\\n self,\\n walletPubKeyHash,\\n mainUtxo\\n );\\n\\n // Process the redemption transaction input. Specifically, check if it\\n // refers to the expected wallet's main UTXO.\\n OutboundTx.processWalletOutboundTxInput(\\n self,\\n redemptionTx.inputVector,\\n mainUtxo\\n );\\n\\n // Process redemption transaction outputs to extract some info required\\n // for further processing.\\n RedemptionTxOutputsInfo memory outputsInfo = processRedemptionTxOutputs(\\n self,\\n redemptionTx.outputVector,\\n walletPubKeyHash\\n );\\n\\n require(\\n mainUtxo.txOutputValue - outputsInfo.outputsTotalValue <=\\n self.redemptionTxMaxTotalFee,\\n \\\"Transaction fee is too high\\\"\\n );\\n\\n if (outputsInfo.changeValue > 0) {\\n // If the change value is grater than zero, it means the change\\n // output exists and can be used as new wallet's main UTXO.\\n wallet.mainUtxoHash = keccak256(\\n abi.encodePacked(\\n redemptionTxHash,\\n outputsInfo.changeIndex,\\n outputsInfo.changeValue\\n )\\n );\\n } else {\\n // If the change value is zero, it means the change output doesn't\\n // exists and no funds left on the wallet. Delete the main UTXO\\n // for that wallet to represent that state in a proper way.\\n delete wallet.mainUtxoHash;\\n }\\n\\n wallet.pendingRedemptionsValue -= outputsInfo.totalBurnableValue;\\n\\n emit RedemptionsCompleted(walletPubKeyHash, redemptionTxHash);\\n\\n self.bank.decreaseBalance(outputsInfo.totalBurnableValue);\\n\\n if (outputsInfo.totalTreasuryFee > 0) {\\n self.bank.transferBalance(\\n self.treasury,\\n outputsInfo.totalTreasuryFee\\n );\\n }\\n }\\n\\n /// @notice Resolves redeeming wallet based on the provided wallet public\\n /// key 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.\\n /// @return wallet Data of the sweeping wallet.\\n /// @dev Requirements:\\n /// - Sweeping wallet must be either in Live or MovingFunds state,\\n /// - Main UTXO of the redeeming wallet must exists in the storage,\\n /// - The passed `mainUTXO` parameter must be equal to the stored one.\\n function resolveRedeemingWallet(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) internal view returns (Wallets.Wallet storage wallet) {\\n wallet = self.registeredWallets[walletPubKeyHash];\\n\\n // Assert that main UTXO for passed wallet exists in storage.\\n bytes32 mainUtxoHash = wallet.mainUtxoHash;\\n require(mainUtxoHash != bytes32(0), \\\"No main UTXO for given wallet\\\");\\n\\n // Assert that passed main UTXO parameter is the same as in storage and\\n // can be used for further processing.\\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\\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\\n /// @notice Processes the Bitcoin redemption transaction output vector.\\n /// It extracts each output and tries to identify it as a pending\\n /// redemption request, reported timed out request, or change.\\n /// Reverts if one of the outputs cannot be recognized properly.\\n /// This function also marks each request as processed by removing\\n /// them from `pendingRedemptions` mapping.\\n /// @param redemptionTxOutputVector Bitcoin redemption transaction output\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVout` function\\n /// before it is passed here.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @return info Outcomes of the processing.\\n function processRedemptionTxOutputs(\\n BridgeState.Storage storage self,\\n bytes memory redemptionTxOutputVector,\\n bytes20 walletPubKeyHash\\n ) internal returns (RedemptionTxOutputsInfo memory info) {\\n // Determining the total number of redemption transaction outputs in\\n // the same way as for number of inputs. See `BitcoinTx.outputVector`\\n // docs for more details.\\n (\\n uint256 outputsCompactSizeUintLength,\\n uint256 outputsCount\\n ) = redemptionTxOutputVector.parseVarInt();\\n\\n // To determine the first output starting index, we must jump over\\n // the compactSize uint which prepends the output 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 outputStartingIndex = 1 + outputsCompactSizeUintLength;\\n\\n // Calculate the keccak256 for two possible wallet's P2PKH or P2WPKH\\n // scripts that can be used to lock the change. This is done upfront to\\n // save on gas. Both scripts have a strict format defined by Bitcoin.\\n //\\n // The P2PKH script has the byte format: <0x1976a914> <20-byte PKH> <0x88ac>.\\n // According to 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 walletP2PKHScriptKeccak = keccak256(\\n abi.encodePacked(BitcoinTx.makeP2PKHScript(walletPubKeyHash))\\n );\\n // The P2WPKH script has the byte format: <0x160014> <20-byte PKH>.\\n // According to 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 walletP2WPKHScriptKeccak = keccak256(\\n abi.encodePacked(BitcoinTx.makeP2WPKHScript(walletPubKeyHash))\\n );\\n\\n return\\n processRedemptionTxOutputs(\\n self,\\n redemptionTxOutputVector,\\n walletPubKeyHash,\\n RedemptionTxOutputsProcessingInfo(\\n outputStartingIndex,\\n outputsCount,\\n walletP2PKHScriptKeccak,\\n walletP2WPKHScriptKeccak\\n )\\n );\\n }\\n\\n /// @notice Processes all outputs from the redemption transaction. Tries to\\n /// identify output as a change output, pending redemption request\\n /// or reported redemption. Reverts if one of the outputs cannot be\\n /// recognized properly. Marks each request as processed by removing\\n /// them from `pendingRedemptions` mapping.\\n /// @param redemptionTxOutputVector Bitcoin redemption transaction output\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVout` function\\n /// before it is passed here.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @param processInfo RedemptionTxOutputsProcessingInfo identifying output\\n /// starting index, the number of outputs and possible wallet change\\n /// P2PKH and P2WPKH scripts.\\n function processRedemptionTxOutputs(\\n BridgeState.Storage storage self,\\n bytes memory redemptionTxOutputVector,\\n bytes20 walletPubKeyHash,\\n RedemptionTxOutputsProcessingInfo memory processInfo\\n ) internal returns (RedemptionTxOutputsInfo memory resultInfo) {\\n // Helper flag indicating whether there was at least one redemption\\n // output present (redemption must be either pending or reported as\\n // timed out).\\n bool redemptionPresent = false;\\n\\n // Outputs processing loop.\\n for (uint256 i = 0; i < processInfo.outputsCount; i++) {\\n uint256 outputLength = redemptionTxOutputVector\\n .determineOutputLengthAt(processInfo.outputStartingIndex);\\n\\n // Extract the value from given output.\\n uint64 outputValue = redemptionTxOutputVector.extractValueAt(\\n processInfo.outputStartingIndex\\n );\\n\\n // The output consists of an 8-byte value and a variable length\\n // script. To hash that script we slice the output starting from\\n // 9th byte until the end.\\n uint256 scriptLength = outputLength - 8;\\n uint256 outputScriptStart = processInfo.outputStartingIndex + 8;\\n\\n bytes32 outputScriptHash;\\n /* solhint-disable-next-line no-inline-assembly */\\n assembly {\\n // The first argument to assembly keccak256 is the pointer.\\n // We point to `redemptionTxOutputVector` but at the position\\n // indicated by `outputScriptStart`. To load that position, we\\n // need to call `add(outputScriptStart, 32)` because\\n // `outputScriptStart` has 32 bytes.\\n outputScriptHash := keccak256(\\n add(redemptionTxOutputVector, add(outputScriptStart, 32)),\\n scriptLength\\n )\\n }\\n\\n if (\\n resultInfo.changeValue == 0 &&\\n (outputScriptHash == processInfo.walletP2PKHScriptKeccak ||\\n outputScriptHash == processInfo.walletP2WPKHScriptKeccak) &&\\n outputValue > 0\\n ) {\\n // If we entered here, that means the change output with a\\n // proper non-zero value was found.\\n resultInfo.changeIndex = uint32(i);\\n resultInfo.changeValue = outputValue;\\n } else {\\n // If we entered here, that the means the given output is\\n // supposed to represent a redemption.\\n (\\n uint64 burnableValue,\\n uint64 treasuryFee\\n ) = processNonChangeRedemptionTxOutput(\\n self,\\n _getRedemptionKey(walletPubKeyHash, outputScriptHash),\\n outputValue\\n );\\n resultInfo.totalBurnableValue += burnableValue;\\n resultInfo.totalTreasuryFee += treasuryFee;\\n redemptionPresent = true;\\n }\\n\\n resultInfo.outputsTotalValue += outputValue;\\n\\n // Make the `outputStartingIndex` pointing to the next output by\\n // increasing it by current output's length.\\n processInfo.outputStartingIndex += outputLength;\\n }\\n\\n // Protect against the cases when there is only a single change output\\n // referring back to the wallet PKH and just burning main UTXO value\\n // for transaction fees.\\n require(\\n redemptionPresent,\\n \\\"Redemption transaction must process at least one redemption\\\"\\n );\\n }\\n\\n /// @notice Processes a single redemption transaction output. Tries to\\n /// identify output as a pending redemption request or reported\\n /// redemption timeout. Output script passed to this function must\\n /// not be the change output. Such output needs to be identified\\n /// separately before calling this function.\\n /// Reverts if output is neither requested pending redemption nor\\n /// requested and reported timed-out redemption.\\n /// This function also marks each pending request as processed by\\n /// removing them from `pendingRedemptions` mapping.\\n /// @param redemptionKey Redemption key of the output being processed.\\n /// @param outputValue Value of the output being processed.\\n /// @return burnableValue The value burnable as a result of processing this\\n /// single redemption output. This value needs to be summed up with\\n /// burnable values of all other outputs to evaluate total burnable\\n /// value for the entire redemption transaction. This value is 0\\n /// for a timed-out redemption request.\\n /// @return treasuryFee The treasury fee from this single redemption output.\\n /// This value needs to be summed up with treasury fees of all other\\n /// outputs to evaluate the total treasury fee for the entire\\n /// redemption transaction. This value is 0 for a timed-out\\n /// redemption request.\\n /// @dev Requirements:\\n /// - This function should be called only if the given output\\n /// represents redemption. It must not be the change output.\\n function processNonChangeRedemptionTxOutput(\\n BridgeState.Storage storage self,\\n uint256 redemptionKey,\\n uint64 outputValue\\n ) internal returns (uint64 burnableValue, uint64 treasuryFee) {\\n if (self.pendingRedemptions[redemptionKey].requestedAt != 0) {\\n // If we entered here, that means the output was identified\\n // as a pending redemption request.\\n RedemptionRequest storage request = self.pendingRedemptions[\\n redemptionKey\\n ];\\n // Compute the request's redeemable amount as the requested\\n // amount reduced by the treasury fee. The request's\\n // minimal amount is then the redeemable amount reduced by\\n // the maximum transaction fee.\\n uint64 redeemableAmount = request.requestedAmount -\\n request.treasuryFee;\\n // Output value must fit between the request's redeemable\\n // and minimal amounts to be deemed valid.\\n require(\\n redeemableAmount - request.txMaxFee <= outputValue &&\\n outputValue <= redeemableAmount,\\n \\\"Output value is not within the acceptable range of the pending request\\\"\\n );\\n // Add the redeemable amount to the total burnable value\\n // the Bridge will use to decrease its balance in the Bank.\\n burnableValue = redeemableAmount;\\n // Add the request's treasury fee to the total treasury fee\\n // value the Bridge will transfer to the treasury.\\n treasuryFee = request.treasuryFee;\\n // Request was properly handled so remove its redemption\\n // key from the mapping to make it reusable for further\\n // requests.\\n delete self.pendingRedemptions[redemptionKey];\\n } else {\\n // If we entered here, the output is not a redemption\\n // request but there is still a chance the given output is\\n // related to a reported timed out redemption request.\\n // If so, check if the output value matches the request\\n // amount to confirm this is an overdue request fulfillment\\n // then bypass this output and process the subsequent\\n // ones. That also means the wallet was already punished\\n // for the inactivity. Otherwise, just revert.\\n RedemptionRequest storage request = self.timedOutRedemptions[\\n redemptionKey\\n ];\\n\\n require(\\n request.requestedAt != 0,\\n \\\"Output is a non-requested redemption\\\"\\n );\\n\\n uint64 redeemableAmount = request.requestedAmount -\\n request.treasuryFee;\\n\\n require(\\n redeemableAmount - request.txMaxFee <= outputValue &&\\n outputValue <= redeemableAmount,\\n \\\"Output value is not within the acceptable range of the timed out request\\\"\\n );\\n\\n delete self.timedOutRedemptions[redemptionKey];\\n }\\n }\\n\\n /// @notice Notifies that there is a pending redemption request associated\\n /// with the given wallet, that has timed out. The redemption\\n /// request is identified by the key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The results of calling this function:\\n /// - the pending redemptions value for the wallet will be decreased\\n /// by the requested amount (minus treasury fee),\\n /// - the tokens taken from the redeemer on redemption request will\\n /// be returned to the redeemer,\\n /// - the request will be moved from pending redemptions to\\n /// timed-out redemptions,\\n /// - if the state of the wallet is `Live` or `MovingFunds`, the\\n /// wallet operators will be slashed and the notifier will be\\n /// rewarded,\\n /// - if the state of wallet is `Live`, the wallet will be closed or\\n /// marked as `MovingFunds` (depending on the presence or absence\\n /// of the wallet's main UTXO) and the wallet will no longer be\\n /// marked as the active wallet (if it was marked as such).\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH).\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Terminated state,\\n /// - The redemption request identified by `walletPubKeyHash` and\\n /// `redeemerOutputScript` must exist,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time defined by `redemptionTimeout` must have\\n /// passed since the redemption was requested (the request must be\\n /// timed-out).\\n function notifyRedemptionTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs,\\n bytes calldata redeemerOutputScript\\n ) external {\\n // Wallet state is validated in `notifyWalletRedemptionTimeout`.\\n uint256 redemptionKey = getRedemptionKey(\\n walletPubKeyHash,\\n redeemerOutputScript\\n );\\n Redemption.RedemptionRequest memory request = self.pendingRedemptions[\\n redemptionKey\\n ];\\n\\n require(request.requestedAt > 0, \\\"Redemption request does not exist\\\");\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n request.requestedAt + self.redemptionTimeout < block.timestamp,\\n \\\"Redemption request has not timed out\\\"\\n );\\n\\n // Update the wallet's pending redemptions value\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n wallet.pendingRedemptionsValue -=\\n request.requestedAmount -\\n request.treasuryFee;\\n\\n // It is worth noting that there is no need to check if\\n // `timedOutRedemption` mapping already contains the given redemption\\n // key. There is no possibility to re-use a key of a reported timed-out\\n // redemption because the wallet responsible for causing the timeout is\\n // moved to a state that prevents it to receive new redemption requests.\\n\\n // Propagate timeout consequences to the wallet\\n self.notifyWalletRedemptionTimeout(walletPubKeyHash, walletMembersIDs);\\n\\n // Move the redemption from pending redemptions to timed-out redemptions\\n self.timedOutRedemptions[redemptionKey] = request;\\n delete self.pendingRedemptions[redemptionKey];\\n\\n // slither-disable-next-line reentrancy-events\\n emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);\\n\\n // Return the requested amount of tokens to the redeemer\\n self.bank.transferBalance(request.redeemer, request.requestedAmount);\\n }\\n\\n /// @notice Calculate redemption key without allocations.\\n /// @param walletPubKeyHash the pubkey hash of the wallet.\\n /// @param script the output script of the redemption.\\n /// @return The key = keccak256(keccak256(script) | walletPubKeyHash).\\n function getRedemptionKey(bytes20 walletPubKeyHash, bytes memory script)\\n internal\\n pure\\n returns (uint256)\\n {\\n bytes32 scriptHash = keccak256(script);\\n uint256 key;\\n /* solhint-disable-next-line no-inline-assembly */\\n assembly {\\n mstore(0, scriptHash)\\n mstore(32, walletPubKeyHash)\\n key := keccak256(0, 52)\\n }\\n return key;\\n }\\n\\n /// @notice Finish calculating redemption key without allocations.\\n /// @param walletPubKeyHash the pubkey hash of the wallet.\\n /// @param scriptHash the output script hash of the redemption.\\n /// @return The key = keccak256(scriptHash | walletPubKeyHash).\\n function _getRedemptionKey(bytes20 walletPubKeyHash, bytes32 scriptHash)\\n internal\\n pure\\n returns (uint256)\\n {\\n uint256 key;\\n /* solhint-disable-next-line no-inline-assembly */\\n assembly {\\n mstore(0, scriptHash)\\n mstore(32, walletPubKeyHash)\\n key := keccak256(0, 52)\\n }\\n return key;\\n }\\n}\\n\",\"keccak256\":\"0x341e2636230764f1bcc75948adedd4c8b38e34a87bb9094b251f9b0870922e93\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Wallets.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {EcdsaDkg} from \\\"@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol\\\";\\nimport {Math} from \\\"@openzeppelin/contracts/utils/math/Math.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./EcdsaLib.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\n\\n/// @title Wallet library\\n/// @notice Library responsible for handling integration between Bridge\\n/// contract and ECDSA wallets.\\nlibrary Wallets {\\n using BTCUtils for bytes;\\n\\n /// @notice Represents wallet state:\\n enum WalletState {\\n /// @dev The wallet is unknown to the Bridge.\\n Unknown,\\n /// @dev The wallet can sweep deposits and accept redemption requests.\\n Live,\\n /// @dev The wallet was deemed unhealthy and is expected to move their\\n /// outstanding funds to another wallet. The wallet can still\\n /// fulfill their pending redemption requests although new\\n /// redemption requests and new deposit reveals are not accepted.\\n MovingFunds,\\n /// @dev The wallet moved or redeemed all their funds and is in the\\n /// closing period where it is still a subject of fraud challenges\\n /// and must defend against them. This state is needed to protect\\n /// against deposit frauds on deposits revealed but not swept.\\n /// The closing period must be greater that the deposit refund\\n /// time plus some time margin.\\n Closing,\\n /// @dev The wallet finalized the closing period successfully and\\n /// can no longer perform any action in the Bridge.\\n Closed,\\n /// @dev The wallet committed a fraud that was reported, did not move\\n /// funds to another wallet before a timeout, or did not sweep\\n /// funds moved to if from another wallet before a timeout. The\\n /// wallet is blocked and can not perform any actions in the Bridge.\\n /// Off-chain coordination with the wallet operators is needed to\\n /// recover funds.\\n Terminated\\n }\\n\\n /// @notice Holds information about a wallet.\\n struct Wallet {\\n // Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry.\\n bytes32 ecdsaWalletID;\\n // Latest wallet's main UTXO hash computed as\\n // keccak256(txHash | txOutputIndex | txOutputValue). The `tx` prefix\\n // refers to the transaction which created that main UTXO. The `txHash`\\n // is `bytes32` (ordered as in Bitcoin internally), `txOutputIndex`\\n // an `uint32`, and `txOutputValue` an `uint64` value.\\n bytes32 mainUtxoHash;\\n // The total redeemable value of pending redemption requests targeting\\n // that wallet.\\n uint64 pendingRedemptionsValue;\\n // UNIX timestamp the wallet was created at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 createdAt;\\n // UNIX timestamp indicating the moment the wallet was requested to\\n // move their funds.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 movingFundsRequestedAt;\\n // UNIX timestamp indicating the moment the wallet's closing period\\n // started.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 closingStartedAt;\\n // Total count of pending moved funds sweep requests targeting this wallet.\\n uint32 pendingMovedFundsSweepRequestsCount;\\n // Current state of the wallet.\\n WalletState state;\\n // Moving funds target wallet commitment submitted by the wallet. It\\n // is built by applying the keccak256 hash over the list of 20-byte\\n // public key hashes of the target wallets.\\n bytes32 movingFundsTargetWalletsCommitmentHash;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event NewWalletRequested();\\n\\n event NewWalletRegistered(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletMovingFunds(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosing(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosed(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletTerminated(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n /// @notice Requests creation of a new wallet. This function just\\n /// forms a request and the creation process is performed\\n /// asynchronously. Outcome of that process should be delivered\\n /// using `registerNewWallet` function.\\n /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - `activeWalletMainUtxo` components must point to the recent main\\n /// UTXO of the given active wallet, as currently known on the\\n /// Ethereum chain. If there is no active wallet at the moment, or\\n /// the active wallet has no main UTXO, this parameter can be\\n /// empty as it is ignored,\\n /// - Wallet creation must not be in progress,\\n /// - If the active wallet is set, one of the following\\n /// conditions must be true:\\n /// - The active wallet BTC balance is above the minimum threshold\\n /// and the active wallet is old enough, i.e. the creation period\\n /// was elapsed since its creation time,\\n /// - The active wallet BTC balance is above the maximum threshold.\\n function requestNewWallet(\\n BridgeState.Storage storage self,\\n BitcoinTx.UTXO calldata activeWalletMainUtxo\\n ) external {\\n require(\\n self.ecdsaWalletRegistry.getWalletCreationState() ==\\n EcdsaDkg.State.IDLE,\\n \\\"Wallet creation already in progress\\\"\\n );\\n\\n bytes20 activeWalletPubKeyHash = self.activeWalletPubKeyHash;\\n\\n // If the active wallet is set, fetch this wallet's details from\\n // storage to perform conditions check. The `registerNewWallet`\\n // function guarantees an active wallet is always one of the\\n // registered ones.\\n if (activeWalletPubKeyHash != bytes20(0)) {\\n uint64 activeWalletBtcBalance = getWalletBtcBalance(\\n self,\\n activeWalletPubKeyHash,\\n activeWalletMainUtxo\\n );\\n uint32 activeWalletCreatedAt = self\\n .registeredWallets[activeWalletPubKeyHash]\\n .createdAt;\\n /* solhint-disable-next-line not-rely-on-time */\\n bool activeWalletOldEnough = block.timestamp >=\\n activeWalletCreatedAt + self.walletCreationPeriod;\\n\\n require(\\n (activeWalletOldEnough &&\\n activeWalletBtcBalance >=\\n self.walletCreationMinBtcBalance) ||\\n activeWalletBtcBalance >= self.walletCreationMaxBtcBalance,\\n \\\"Wallet creation conditions are not met\\\"\\n );\\n }\\n\\n emit NewWalletRequested();\\n\\n self.ecdsaWalletRegistry.requestNewWallet();\\n }\\n\\n /// @notice Registers a new wallet. This function should be called\\n /// after the wallet creation process initiated using\\n /// `requestNewWallet` completes and brings the outcomes.\\n /// @param ecdsaWalletID Wallet's unique identifier.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Given wallet data must not belong to an already registered wallet.\\n function registerNewWallet(\\n BridgeState.Storage storage self,\\n bytes32 ecdsaWalletID,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external {\\n require(\\n msg.sender == address(self.ecdsaWalletRegistry),\\n \\\"Caller is not the ECDSA Wallet Registry\\\"\\n );\\n\\n // Compress wallet's public key and calculate Bitcoin's hash160 of it.\\n bytes20 walletPubKeyHash = bytes20(\\n EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()\\n );\\n\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n require(\\n wallet.state == WalletState.Unknown,\\n \\\"ECDSA wallet has been already registered\\\"\\n );\\n wallet.ecdsaWalletID = ecdsaWalletID;\\n wallet.state = WalletState.Live;\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.createdAt = uint32(block.timestamp);\\n\\n // Set the freshly created wallet as the new active wallet.\\n self.activeWalletPubKeyHash = walletPubKeyHash;\\n\\n self.liveWalletsCount++;\\n\\n emit NewWalletRegistered(ecdsaWalletID, walletPubKeyHash);\\n }\\n\\n /// @notice Handles a notification about a wallet redemption timeout.\\n /// Triggers the wallet moving funds process only if the wallet is\\n /// still in the Live state. That means multiple action timeouts can\\n /// be reported for the same wallet but only the first report\\n /// requests the wallet to move their funds. Executes slashing if\\n /// the wallet is in Live or MovingFunds state. Allows to notify\\n /// redemption timeout also for a Terminated wallet in case the\\n /// redemption was requested before the wallet got terminated.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the `Live`, `MovingFunds`,\\n /// or `Terminated` state.\\n function notifyWalletRedemptionTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n WalletState walletState = wallet.state;\\n\\n require(\\n walletState == WalletState.Live ||\\n walletState == WalletState.MovingFunds ||\\n walletState == WalletState.Terminated,\\n \\\"Wallet must be in Live or MovingFunds or Terminated state\\\"\\n );\\n\\n if (\\n walletState == Wallets.WalletState.Live ||\\n walletState == Wallets.WalletState.MovingFunds\\n ) {\\n // Slash the wallet operators and reward the notifier\\n self.ecdsaWalletRegistry.seize(\\n self.redemptionTimeoutSlashingAmount,\\n self.redemptionTimeoutNotifierRewardMultiplier,\\n msg.sender,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n }\\n\\n if (walletState == WalletState.Live) {\\n moveFunds(self, walletPubKeyHash);\\n }\\n }\\n\\n /// @notice Handles a notification about a wallet heartbeat failure and\\n /// triggers the wallet moving funds process.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Wallet must be in Live state.\\n function notifyWalletHeartbeatFailed(\\n BridgeState.Storage storage self,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external {\\n require(\\n msg.sender == address(self.ecdsaWalletRegistry),\\n \\\"Caller is not the ECDSA Wallet Registry\\\"\\n );\\n\\n // Compress wallet's public key and calculate Bitcoin's hash160 of it.\\n bytes20 walletPubKeyHash = bytes20(\\n EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()\\n );\\n\\n require(\\n self.registeredWallets[walletPubKeyHash].state == WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n moveFunds(self, walletPubKeyHash);\\n }\\n\\n /// @notice Notifies that the wallet is either old enough or has too few\\n /// satoshis left and qualifies to be closed.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - Wallet must not be set as the current active wallet,\\n /// - Wallet must exceed the wallet maximum age OR the wallet BTC\\n /// balance must be lesser than the minimum threshold. If the latter\\n /// case is true, the `walletMainUtxo` components must point to the\\n /// recent main UTXO of the given wallet, as currently known on the\\n /// Ethereum chain. If the wallet has no main UTXO, this parameter\\n /// can be empty as it is ignored since the wallet balance is\\n /// assumed to be zero,\\n /// - Wallet must be in Live state.\\n function notifyWalletCloseable(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo\\n ) external {\\n require(\\n self.activeWalletPubKeyHash != walletPubKeyHash,\\n \\\"Active wallet cannot be considered closeable\\\"\\n );\\n\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n require(\\n wallet.state == WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n bool walletOldEnough = block.timestamp >=\\n wallet.createdAt + self.walletMaxAge;\\n\\n require(\\n walletOldEnough ||\\n getWalletBtcBalance(self, walletPubKeyHash, walletMainUtxo) <\\n self.walletClosureMinBtcBalance,\\n \\\"Wallet needs to be old enough or have too few satoshis\\\"\\n );\\n\\n moveFunds(self, walletPubKeyHash);\\n }\\n\\n /// @notice Notifies about the end of the closing period for the given wallet.\\n /// Closes the wallet ultimately and notifies the ECDSA registry\\n /// about this fact.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the Closing state,\\n /// - The wallet closing period must have elapsed.\\n function notifyWalletClosingPeriodElapsed(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n require(\\n wallet.state == WalletState.Closing,\\n \\\"Wallet must be in Closing state\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >\\n wallet.closingStartedAt + self.walletClosingPeriod,\\n \\\"Closing period has not elapsed yet\\\"\\n );\\n\\n finalizeWalletClosing(self, walletPubKeyHash);\\n }\\n\\n /// @notice Notifies that the wallet completed the moving funds process\\n /// successfully. Checks if the funds were moved to the expected\\n /// target wallets. Closes the source wallet if everything went\\n /// good and reverts otherwise.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param targetWalletsHash 32-byte keccak256 hash over the list of\\n /// 20-byte public key hashes of the target wallets actually used\\n /// within the moving funds transactions.\\n /// @dev Requirements:\\n /// - The caller must make sure the moving funds transaction actually\\n /// happened on Bitcoin chain and fits the protocol requirements,\\n /// - The source wallet must be in the MovingFunds state,\\n /// - The target wallets commitment must be submitted by the source\\n /// wallet,\\n /// - The actual target wallets used in the moving funds transaction\\n /// must be exactly the same as the target wallets commitment.\\n function notifyWalletFundsMoved(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n bytes32 targetWalletsHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n // Check that the wallet is in the MovingFunds state but don't check\\n // if the moving funds timeout is exceeded. That should give a\\n // possibility to move funds in case when timeout was hit but was\\n // not reported yet.\\n require(\\n wallet.state == WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n bytes32 targetWalletsCommitmentHash = wallet\\n .movingFundsTargetWalletsCommitmentHash;\\n\\n require(\\n targetWalletsCommitmentHash != bytes32(0),\\n \\\"Target wallets commitment not submitted yet\\\"\\n );\\n\\n // Make sure that the target wallets where funds were moved to are\\n // exactly the same as the ones the source wallet committed to.\\n require(\\n targetWalletsCommitmentHash == targetWalletsHash,\\n \\\"Target wallets don't correspond to the commitment\\\"\\n );\\n\\n // If funds were moved, the wallet has no longer a main UTXO.\\n delete wallet.mainUtxoHash;\\n\\n beginWalletClosing(self, walletPubKeyHash);\\n }\\n\\n /// @notice Called when a MovingFunds wallet has a balance below the dust\\n /// threshold. Begins the wallet closing.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state.\\n function notifyWalletMovingFundsBelowDust(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n WalletState walletState = self\\n .registeredWallets[walletPubKeyHash]\\n .state;\\n\\n require(\\n walletState == Wallets.WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n beginWalletClosing(self, walletPubKeyHash);\\n }\\n\\n /// @notice Called when the timeout for MovingFunds for the wallet elapsed.\\n /// Slashes wallet members and terminates the wallet.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state.\\n function notifyWalletMovingFundsTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) internal {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n self.ecdsaWalletRegistry.seize(\\n self.movingFundsTimeoutSlashingAmount,\\n self.movingFundsTimeoutNotifierRewardMultiplier,\\n msg.sender,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n\\n terminateWallet(self, walletPubKeyHash);\\n }\\n\\n /// @notice Called when a wallet which was asked to sweep funds moved from\\n /// another wallet did not provide a sweeping proof before a timeout.\\n /// Slashes and terminates the wallet who failed to provide a proof.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet which was\\n /// supposed to sweep funds.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the `Live`, `MovingFunds`,\\n /// or `Terminated` state.\\n function notifyWalletMovedFundsSweepTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n WalletState walletState = wallet.state;\\n\\n require(\\n walletState == WalletState.Live ||\\n walletState == WalletState.MovingFunds ||\\n walletState == WalletState.Terminated,\\n \\\"Wallet must be in Live or MovingFunds or Terminated state\\\"\\n );\\n\\n if (\\n walletState == Wallets.WalletState.Live ||\\n walletState == Wallets.WalletState.MovingFunds\\n ) {\\n self.ecdsaWalletRegistry.seize(\\n self.movedFundsSweepTimeoutSlashingAmount,\\n self.movedFundsSweepTimeoutNotifierRewardMultiplier,\\n msg.sender,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n\\n terminateWallet(self, walletPubKeyHash);\\n }\\n }\\n\\n /// @notice Called when a wallet which was challenged for a fraud did not\\n /// defeat the challenge before the timeout. Slashes and terminates\\n /// the wallet who failed to defeat the challenge. If the wallet is\\n /// already terminated, it does nothing.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet which was\\n /// supposed to sweep funds.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param challenger Address of the party which submitted the fraud\\n /// challenge.\\n /// @dev Requirements:\\n /// - The wallet must be in the `Live`, `MovingFunds`, `Closing`\\n /// or `Terminated` state.\\n function notifyWalletFraudChallengeDefeatTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs,\\n address challenger\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n WalletState walletState = wallet.state;\\n\\n if (\\n walletState == Wallets.WalletState.Live ||\\n walletState == Wallets.WalletState.MovingFunds ||\\n walletState == Wallets.WalletState.Closing\\n ) {\\n self.ecdsaWalletRegistry.seize(\\n self.fraudSlashingAmount,\\n self.fraudNotifierRewardMultiplier,\\n challenger,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n\\n terminateWallet(self, walletPubKeyHash);\\n } else if (walletState == Wallets.WalletState.Terminated) {\\n // This is a special case when the wallet was already terminated\\n // due to a previous deliberate protocol violation. In that\\n // case, this function should be still callable for other fraud\\n // challenges timeouts in order to let the challenger unlock its\\n // ETH deposit back. However, the wallet termination logic is\\n // not called and the challenger is not rewarded.\\n } else {\\n revert(\\n \\\"Wallet must be in Live or MovingFunds or Closing or Terminated state\\\"\\n );\\n }\\n }\\n\\n /// @notice Requests a wallet to move their funds. If the wallet balance\\n /// is zero, the wallet closing begins immediately. If the move\\n /// funds request refers to the current active wallet, such a wallet\\n /// is no longer considered active and the active wallet slot\\n /// is unset allowing to trigger a new wallet creation immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the Live state.\\n function moveFunds(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n if (wallet.mainUtxoHash == bytes32(0)) {\\n // If the wallet has no main UTXO, that means its BTC balance\\n // is zero and the wallet closing should begin immediately.\\n beginWalletClosing(self, walletPubKeyHash);\\n } else {\\n // Otherwise, initialize the moving funds process.\\n wallet.state = WalletState.MovingFunds;\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.movingFundsRequestedAt = uint32(block.timestamp);\\n\\n // slither-disable-next-line reentrancy-events\\n emit WalletMovingFunds(wallet.ecdsaWalletID, walletPubKeyHash);\\n }\\n\\n if (self.activeWalletPubKeyHash == walletPubKeyHash) {\\n // If the move funds request refers to the current active wallet,\\n // unset the active wallet and make the wallet creation process\\n // possible in order to get a new healthy active wallet.\\n delete self.activeWalletPubKeyHash;\\n }\\n\\n self.liveWalletsCount--;\\n }\\n\\n /// @notice Begins the closing period of the given wallet.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the\\n /// MovingFunds state.\\n function beginWalletClosing(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n // Initialize the closing period.\\n wallet.state = WalletState.Closing;\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.closingStartedAt = uint32(block.timestamp);\\n\\n // slither-disable-next-line reentrancy-events\\n emit WalletClosing(wallet.ecdsaWalletID, walletPubKeyHash);\\n }\\n\\n /// @notice Finalizes the closing period of the given wallet and notifies\\n /// the ECDSA registry about this fact.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the Closing state.\\n function finalizeWalletClosing(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n wallet.state = WalletState.Closed;\\n\\n emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);\\n\\n self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);\\n }\\n\\n /// @notice Terminates the given wallet and notifies the ECDSA registry\\n /// about this fact. If the wallet termination refers to the current\\n /// active wallet, such a wallet is no longer considered active and\\n /// the active wallet slot is unset allowing to trigger a new wallet\\n /// creation immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the\\n /// Live or MovingFunds or Closing state.\\n function terminateWallet(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n if (wallet.state == WalletState.Live) {\\n self.liveWalletsCount--;\\n }\\n\\n wallet.state = WalletState.Terminated;\\n\\n // slither-disable-next-line reentrancy-events\\n emit WalletTerminated(wallet.ecdsaWalletID, walletPubKeyHash);\\n\\n if (self.activeWalletPubKeyHash == walletPubKeyHash) {\\n // If termination refers to the current active wallet,\\n // unset the active wallet and make the wallet creation process\\n // possible in order to get a new healthy active wallet.\\n delete self.activeWalletPubKeyHash;\\n }\\n\\n self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);\\n }\\n\\n /// @notice Gets BTC balance for given the wallet.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\n /// @return walletBtcBalance Current BTC balance for the given wallet.\\n /// @dev Requirements:\\n /// - `walletMainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain.\\n /// If the wallet has no main UTXO, this parameter can be empty as it\\n /// is ignored.\\n function getWalletBtcBalance(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo\\n ) internal view returns (uint64 walletBtcBalance) {\\n bytes32 walletMainUtxoHash = self\\n .registeredWallets[walletPubKeyHash]\\n .mainUtxoHash;\\n\\n // If the wallet has a main UTXO hash set, cross-check it with the\\n // provided plain-text parameter and get the transaction output value\\n // as BTC balance. Otherwise, the BTC balance is just zero.\\n if (walletMainUtxoHash != bytes32(0)) {\\n require(\\n keccak256(\\n abi.encodePacked(\\n walletMainUtxo.txHash,\\n walletMainUtxo.txOutputIndex,\\n walletMainUtxo.txOutputValue\\n )\\n ) == walletMainUtxoHash,\\n \\\"Invalid wallet main UTXO data\\\"\\n );\\n\\n walletBtcBalance = walletMainUtxo.txOutputValue;\\n }\\n\\n return walletBtcBalance;\\n }\\n}\\n\",\"keccak256\":\"0x7d0ddff8dc20c10b1e62c8dba304c928c8d8de1c8b6c4d3a1c557dae99743435\",\"license\":\"GPL-3.0-only\"},\"contracts/token/TBTC.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\\n constructor() ERC20WithPermit(\\\"tBTC v2\\\", \\\"tBTC\\\") {}\\n}\\n\",\"keccak256\":\"0x4542aaa48f7682005b815253768b6433c811daff5d2727db256d5f1879f5ccbf\",\"license\":\"GPL-3.0-only\"},\"contracts/vault/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"../bank/IReceiveBalanceApproval.sol\\\";\\n\\n/// @title Bank Vault interface\\n/// @notice `IVault` is an interface for a smart contract consuming Bank\\n/// balances of other contracts or externally owned accounts (EOA).\\ninterface IVault is IReceiveBalanceApproval {\\n /// @notice Called by the Bank in `increaseBalanceAndCall` function after\\n /// increasing the balance in the Bank for the vault. It happens in\\n /// the same transaction in which deposits were swept by the Bridge.\\n /// This allows the depositor to route their deposit revealed to the\\n /// Bridge to the particular smart contract (vault) in the same\\n /// transaction in which the deposit is revealed. This way, the\\n /// depositor does not have to execute additional transaction after\\n /// the deposit gets swept by the Bridge to approve and transfer\\n /// their balance to the vault.\\n /// @param depositors Addresses of depositors whose deposits have been swept.\\n /// @param depositedAmounts Amounts deposited by individual depositors and\\n /// swept.\\n /// @dev The implementation must ensure this function can only be called\\n /// by the Bank. The Bank guarantees that the vault's balance was\\n /// increased by the sum of all deposited amounts before this function\\n /// is called, in the same transaction.\\n function receiveBalanceIncrease(\\n address[] calldata depositors,\\n uint256[] calldata depositedAmounts\\n ) external;\\n}\\n\",\"keccak256\":\"0x12866d625abab349324ee28c6f6ec0114eaa7069ea0c5f7c7b23f6a0f833ae60\",\"license\":\"GPL-3.0-only\"},\"contracts/vault/TBTCOptimisticMinting.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"../bridge/Bridge.sol\\\";\\nimport \\\"../bridge/Deposit.sol\\\";\\nimport \\\"../GovernanceUtils.sol\\\";\\n\\n/// @title TBTC Optimistic Minting\\n/// @notice The Optimistic Minting mechanism allows to mint TBTC before\\n/// `TBTCVault` receives the Bank balance. There are two permissioned\\n/// sets in the system: Minters and Guardians, both set up in 1-of-n\\n/// mode. Minters observe the revealed deposits and request minting TBTC.\\n/// Any single Minter can perform this action. There is an\\n/// `optimisticMintingDelay` between the time of the request from\\n/// a Minter to the time TBTC is minted. During the time of the delay,\\n/// any Guardian can cancel the minting.\\n/// @dev This functionality is a part of `TBTCVault`. It is implemented in\\n/// a separate abstract contract to achieve better separation of concerns\\n/// and easier-to-follow code.\\nabstract contract TBTCOptimisticMinting is Ownable {\\n // Represents optimistic minting request for the given deposit revealed\\n // to the Bridge.\\n struct OptimisticMintingRequest {\\n // UNIX timestamp at which the optimistic minting was requested.\\n uint64 requestedAt;\\n // UNIX timestamp at which the optimistic minting was finalized.\\n // 0 if not yet finalized.\\n uint64 finalizedAt;\\n }\\n\\n /// @notice The time delay that needs to pass between initializing and\\n /// finalizing the upgrade of governable parameters.\\n uint256 public constant GOVERNANCE_DELAY = 24 hours;\\n\\n /// @notice Multiplier to convert satoshi to TBTC token units.\\n uint256 public constant SATOSHI_MULTIPLIER = 10**10;\\n\\n Bridge public immutable bridge;\\n\\n /// @notice Indicates if the optimistic minting has been paused. Only the\\n /// Governance can pause optimistic minting. Note that the pause of\\n /// the optimistic minting does not stop the standard minting flow\\n /// where wallets sweep deposits.\\n bool public isOptimisticMintingPaused;\\n\\n /// @notice Divisor used to compute the treasury fee taken from each\\n /// optimistically minted deposit and transferred to the treasury\\n /// upon finalization of the optimistic mint. This fee is computed\\n /// as follows: `fee = amount / optimisticMintingFeeDivisor`.\\n /// For example, if the fee needs to be 2%, the\\n /// `optimisticMintingFeeDivisor` should be set to `50` because\\n /// `1/50 = 0.02 = 2%`.\\n /// The optimistic minting fee does not replace the deposit treasury\\n /// fee cut by the Bridge. The optimistic fee is a percentage AFTER\\n /// the treasury fee is cut:\\n /// `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`\\n uint32 public optimisticMintingFeeDivisor = 500; // 1/500 = 0.002 = 0.2%\\n\\n /// @notice The time that needs to pass between the moment the optimistic\\n /// minting is requested and the moment optimistic minting is\\n /// finalized with minting TBTC.\\n uint32 public optimisticMintingDelay = 3 hours;\\n\\n /// @notice Indicates if the given address is a Minter. Only Minters can\\n /// request optimistic minting.\\n mapping(address => bool) public isMinter;\\n\\n /// @notice List of all Minters.\\n /// @dev May be used to establish an order in which the Minters should\\n /// request for an optimistic minting.\\n address[] public minters;\\n\\n /// @notice Indicates if the given address is a Guardian. Only Guardians can\\n /// cancel requested optimistic minting.\\n mapping(address => bool) public isGuardian;\\n\\n /// @notice Collection of all revealed deposits for which the optimistic\\n /// minting was requested. Indexed by a deposit key computed as\\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\\n mapping(uint256 => OptimisticMintingRequest)\\n public optimisticMintingRequests;\\n\\n /// @notice Optimistic minting debt value per depositor's address. The debt\\n /// represents the total value of all depositor's deposits revealed\\n /// to the Bridge that has not been yet swept and led to the\\n /// optimistic minting of TBTC. When `TBTCVault` sweeps a deposit,\\n /// the debt is fully or partially paid off, no matter if that\\n /// particular swept deposit was used for the optimistic minting or\\n /// not. The values are in 1e18 Ethereum precision.\\n mapping(address => uint256) public optimisticMintingDebt;\\n\\n /// @notice New optimistic minting fee divisor value. Set only when the\\n /// parameter update process is pending. Once the update gets\\n // finalized, this will be the value of the divisor.\\n uint32 public newOptimisticMintingFeeDivisor;\\n /// @notice The timestamp at which the update of the optimistic minting fee\\n /// divisor started. Zero if update is not in progress.\\n uint256 public optimisticMintingFeeUpdateInitiatedTimestamp;\\n\\n /// @notice New optimistic minting delay value. Set only when the parameter\\n /// update process is pending. Once the update gets finalized, this\\n // will be the value of the delay.\\n uint32 public newOptimisticMintingDelay;\\n /// @notice The timestamp at which the update of the optimistic minting\\n /// delay started. Zero if update is not in progress.\\n uint256 public optimisticMintingDelayUpdateInitiatedTimestamp;\\n\\n event OptimisticMintingRequested(\\n address indexed minter,\\n uint256 indexed depositKey,\\n address indexed depositor,\\n uint256 amount, // amount in 1e18 Ethereum precision\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n );\\n event OptimisticMintingFinalized(\\n address indexed minter,\\n uint256 indexed depositKey,\\n address indexed depositor,\\n uint256 optimisticMintingDebt\\n );\\n event OptimisticMintingCancelled(\\n address indexed guardian,\\n uint256 indexed depositKey\\n );\\n event OptimisticMintingDebtRepaid(\\n address indexed depositor,\\n uint256 optimisticMintingDebt\\n );\\n event MinterAdded(address indexed minter);\\n event MinterRemoved(address indexed minter);\\n event GuardianAdded(address indexed guardian);\\n event GuardianRemoved(address indexed guardian);\\n event OptimisticMintingPaused();\\n event OptimisticMintingUnpaused();\\n\\n event OptimisticMintingFeeUpdateStarted(\\n uint32 newOptimisticMintingFeeDivisor\\n );\\n event OptimisticMintingFeeUpdated(uint32 newOptimisticMintingFeeDivisor);\\n\\n event OptimisticMintingDelayUpdateStarted(uint32 newOptimisticMintingDelay);\\n event OptimisticMintingDelayUpdated(uint32 newOptimisticMintingDelay);\\n\\n modifier onlyMinter() {\\n require(isMinter[msg.sender], \\\"Caller is not a minter\\\");\\n _;\\n }\\n\\n modifier onlyGuardian() {\\n require(isGuardian[msg.sender], \\\"Caller is not a guardian\\\");\\n _;\\n }\\n\\n modifier onlyOwnerOrGuardian() {\\n require(\\n owner() == msg.sender || isGuardian[msg.sender],\\n \\\"Caller is not the owner or guardian\\\"\\n );\\n _;\\n }\\n\\n modifier whenOptimisticMintingNotPaused() {\\n require(!isOptimisticMintingPaused, \\\"Optimistic minting paused\\\");\\n _;\\n }\\n\\n modifier onlyAfterGovernanceDelay(uint256 updateInitiatedTimestamp) {\\n GovernanceUtils.onlyAfterGovernanceDelay(\\n updateInitiatedTimestamp,\\n GOVERNANCE_DELAY\\n );\\n _;\\n }\\n\\n constructor(Bridge _bridge) {\\n require(\\n address(_bridge) != address(0),\\n \\\"Bridge can not be the zero address\\\"\\n );\\n\\n bridge = _bridge;\\n }\\n\\n /// @dev Mints the given amount of TBTC to the given depositor's address.\\n /// Implemented by TBTCVault.\\n function _mint(address minter, uint256 amount) internal virtual;\\n\\n /// @notice Allows to fetch a list of all Minters.\\n function getMinters() external view returns (address[] memory) {\\n return minters;\\n }\\n\\n /// @notice Allows a Minter to request for an optimistic minting of TBTC.\\n /// The following conditions must be met:\\n /// - There is no optimistic minting request for the deposit,\\n /// finalized or not.\\n /// - The deposit with the given Bitcoin funding transaction hash\\n /// and output index has been revealed to the Bridge.\\n /// - The deposit has not been swept yet.\\n /// - The deposit is targeted into the TBTCVault.\\n /// - The optimistic minting is not paused.\\n /// After calling this function, the Minter has to wait for\\n /// `optimisticMintingDelay` before finalizing the mint with a call\\n /// to finalizeOptimisticMint.\\n /// @dev The deposit done on the Bitcoin side must be revealed early enough\\n /// to the Bridge on Ethereum to pass the Bridge's validation. The\\n /// validation passes successfully only if the deposit reveal is done\\n /// respectively earlier than the moment when the deposit refund\\n /// locktime is reached, i.e. the deposit becomes refundable. It may\\n /// happen that the wallet does not sweep a revealed deposit and one of\\n /// the Minters requests an optimistic mint for that deposit just\\n /// before the locktime is reached. Guardians must cancel optimistic\\n /// minting for this deposit because the wallet will not be able to\\n /// sweep it. The on-chain optimistic minting code does not perform any\\n /// validation for gas efficiency: it would have to perform the same\\n /// validation as `validateDepositRefundLocktime` and expect the entire\\n /// `DepositRevealInfo` to be passed to assemble the expected script\\n /// hash on-chain. Guardians must validate if the deposit happened on\\n /// Bitcoin, that the script hash has the expected format, and that the\\n /// wallet is an active one so they can also validate the time left for\\n /// the refund.\\n function requestOptimisticMint(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) external onlyMinter whenOptimisticMintingNotPaused {\\n uint256 depositKey = calculateDepositKey(\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n\\n OptimisticMintingRequest storage request = optimisticMintingRequests[\\n depositKey\\n ];\\n require(\\n request.requestedAt == 0,\\n \\\"Optimistic minting already requested for the deposit\\\"\\n );\\n\\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\\n\\n require(deposit.revealedAt != 0, \\\"The deposit has not been revealed\\\");\\n require(deposit.sweptAt == 0, \\\"The deposit is already swept\\\");\\n require(deposit.vault == address(this), \\\"Unexpected vault address\\\");\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n request.requestedAt = uint64(block.timestamp);\\n\\n emit OptimisticMintingRequested(\\n msg.sender,\\n depositKey,\\n deposit.depositor,\\n deposit.amount * SATOSHI_MULTIPLIER,\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n }\\n\\n /// @notice Allows a Minter to finalize previously requested optimistic\\n /// minting. The following conditions must be met:\\n /// - The optimistic minting has been requested for the given\\n /// deposit.\\n /// - The deposit has not been swept yet.\\n /// - At least `optimisticMintingDelay` passed since the optimistic\\n /// minting was requested for the given deposit.\\n /// - The optimistic minting has not been finalized earlier for the\\n /// given deposit.\\n /// - The optimistic minting request for the given deposit has not\\n /// been canceled by a Guardian.\\n /// - The optimistic minting is not paused.\\n /// This function mints TBTC and increases `optimisticMintingDebt`\\n /// for the given depositor. The optimistic minting request is\\n /// marked as finalized.\\n function finalizeOptimisticMint(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) external onlyMinter whenOptimisticMintingNotPaused {\\n uint256 depositKey = calculateDepositKey(\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n\\n OptimisticMintingRequest storage request = optimisticMintingRequests[\\n depositKey\\n ];\\n require(\\n request.requestedAt != 0,\\n \\\"Optimistic minting not requested for the deposit\\\"\\n );\\n require(\\n request.finalizedAt == 0,\\n \\\"Optimistic minting already finalized for the deposit\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp > request.requestedAt + optimisticMintingDelay,\\n \\\"Optimistic minting delay has not passed yet\\\"\\n );\\n\\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\\n require(deposit.sweptAt == 0, \\\"The deposit is already swept\\\");\\n\\n // Bridge, when sweeping, cuts a deposit treasury fee and splits\\n // Bitcoin miner fee for the sweep transaction evenly between the\\n // depositors in the sweep.\\n //\\n // When tokens are optimistically minted, we do not know what the\\n // Bitcoin miner fee for the sweep transaction will look like.\\n // The Bitcoin miner fee is ignored. When sweeping, the miner fee is\\n // subtracted so the optimisticMintingDebt may stay non-zero after the\\n // deposit is swept.\\n //\\n // This imbalance is supposed to be solved by a donation to the Bridge.\\n uint256 amountToMint = (deposit.amount - deposit.treasuryFee) *\\n SATOSHI_MULTIPLIER;\\n\\n // The Optimistic Minting mechanism may additionally cut a fee from the\\n // amount that is left after deducting the Bridge deposit treasury fee.\\n // Think of this fee as an extra payment for faster processing of\\n // deposits. One does not need to use the Optimistic Minting mechanism\\n // and they may wait for the Bridge to sweep their deposit if they do\\n // not want to pay the Optimistic Minting fee.\\n uint256 optimisticMintFee = optimisticMintingFeeDivisor > 0\\n ? (amountToMint / optimisticMintingFeeDivisor)\\n : 0;\\n\\n // Both the optimistic minting fee and the share that goes to the\\n // depositor are optimistically minted. All TBTC that is optimistically\\n // minted should be added to the optimistic minting debt. When the\\n // deposit is swept, it is paying off both the depositor's share and the\\n // treasury's share (optimistic minting fee).\\n uint256 newDebt = optimisticMintingDebt[deposit.depositor] +\\n amountToMint;\\n optimisticMintingDebt[deposit.depositor] = newDebt;\\n\\n _mint(deposit.depositor, amountToMint - optimisticMintFee);\\n if (optimisticMintFee > 0) {\\n _mint(bridge.treasury(), optimisticMintFee);\\n }\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n request.finalizedAt = uint64(block.timestamp);\\n\\n emit OptimisticMintingFinalized(\\n msg.sender,\\n depositKey,\\n deposit.depositor,\\n newDebt\\n );\\n }\\n\\n /// @notice Allows a Guardian to cancel optimistic minting request. The\\n /// following conditions must be met:\\n /// - The optimistic minting request for the given deposit exists.\\n /// - The optimistic minting request for the given deposit has not\\n /// been finalized yet.\\n /// Optimistic minting request is removed. It is possible to request\\n /// optimistic minting again for the same deposit later.\\n /// @dev Guardians must validate the following conditions for every deposit\\n /// for which the optimistic minting was requested:\\n /// - The deposit happened on Bitcoin side and it has enough\\n /// confirmations.\\n /// - The optimistic minting has been requested early enough so that\\n /// the wallet has enough time to sweep the deposit.\\n /// - The wallet is an active one and it does perform sweeps or it will\\n /// perform sweeps once the sweeps are activated.\\n function cancelOptimisticMint(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) external onlyGuardian {\\n uint256 depositKey = calculateDepositKey(\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n\\n OptimisticMintingRequest storage request = optimisticMintingRequests[\\n depositKey\\n ];\\n require(\\n request.requestedAt != 0,\\n \\\"Optimistic minting not requested for the deposit\\\"\\n );\\n require(\\n request.finalizedAt == 0,\\n \\\"Optimistic minting already finalized for the deposit\\\"\\n );\\n\\n // Delete it. It allows to request optimistic minting for the given\\n // deposit again. Useful in case of an errant Guardian.\\n delete optimisticMintingRequests[depositKey];\\n\\n emit OptimisticMintingCancelled(msg.sender, depositKey);\\n }\\n\\n /// @notice Adds the address to the Minter list.\\n function addMinter(address minter) external onlyOwner {\\n require(!isMinter[minter], \\\"This address is already a minter\\\");\\n isMinter[minter] = true;\\n minters.push(minter);\\n emit MinterAdded(minter);\\n }\\n\\n /// @notice Removes the address from the Minter list.\\n function removeMinter(address minter) external onlyOwnerOrGuardian {\\n require(isMinter[minter], \\\"This address is not a minter\\\");\\n delete isMinter[minter];\\n\\n // We do not expect too many Minters so a simple loop is safe.\\n for (uint256 i = 0; i < minters.length; i++) {\\n if (minters[i] == minter) {\\n minters[i] = minters[minters.length - 1];\\n // slither-disable-next-line costly-loop\\n minters.pop();\\n break;\\n }\\n }\\n\\n emit MinterRemoved(minter);\\n }\\n\\n /// @notice Adds the address to the Guardian set.\\n function addGuardian(address guardian) external onlyOwner {\\n require(!isGuardian[guardian], \\\"This address is already a guardian\\\");\\n isGuardian[guardian] = true;\\n emit GuardianAdded(guardian);\\n }\\n\\n /// @notice Removes the address from the Guardian set.\\n function removeGuardian(address guardian) external onlyOwner {\\n require(isGuardian[guardian], \\\"This address is not a guardian\\\");\\n delete isGuardian[guardian];\\n emit GuardianRemoved(guardian);\\n }\\n\\n /// @notice Pauses the optimistic minting. Note that the pause of the\\n /// optimistic minting does not stop the standard minting flow\\n /// where wallets sweep deposits.\\n function pauseOptimisticMinting() external onlyOwner {\\n require(\\n !isOptimisticMintingPaused,\\n \\\"Optimistic minting already paused\\\"\\n );\\n isOptimisticMintingPaused = true;\\n emit OptimisticMintingPaused();\\n }\\n\\n /// @notice Unpauses the optimistic minting.\\n function unpauseOptimisticMinting() external onlyOwner {\\n require(isOptimisticMintingPaused, \\\"Optimistic minting is not paused\\\");\\n isOptimisticMintingPaused = false;\\n emit OptimisticMintingUnpaused();\\n }\\n\\n /// @notice Begins the process of updating optimistic minting fee.\\n /// The fee is computed as follows:\\n /// `fee = amount / optimisticMintingFeeDivisor`.\\n /// For example, if the fee needs to be 2% of each deposit,\\n /// the `optimisticMintingFeeDivisor` should be set to `50` because\\n /// `1/50 = 0.02 = 2%`.\\n /// @dev See the documentation for optimisticMintingFeeDivisor.\\n function beginOptimisticMintingFeeUpdate(\\n uint32 _newOptimisticMintingFeeDivisor\\n ) external onlyOwner {\\n /* solhint-disable-next-line not-rely-on-time */\\n optimisticMintingFeeUpdateInitiatedTimestamp = block.timestamp;\\n newOptimisticMintingFeeDivisor = _newOptimisticMintingFeeDivisor;\\n emit OptimisticMintingFeeUpdateStarted(_newOptimisticMintingFeeDivisor);\\n }\\n\\n /// @notice Finalizes the update process of the optimistic minting fee.\\n function finalizeOptimisticMintingFeeUpdate()\\n external\\n onlyOwner\\n onlyAfterGovernanceDelay(optimisticMintingFeeUpdateInitiatedTimestamp)\\n {\\n optimisticMintingFeeDivisor = newOptimisticMintingFeeDivisor;\\n emit OptimisticMintingFeeUpdated(newOptimisticMintingFeeDivisor);\\n\\n newOptimisticMintingFeeDivisor = 0;\\n optimisticMintingFeeUpdateInitiatedTimestamp = 0;\\n }\\n\\n /// @notice Begins the process of updating optimistic minting delay.\\n function beginOptimisticMintingDelayUpdate(\\n uint32 _newOptimisticMintingDelay\\n ) external onlyOwner {\\n /* solhint-disable-next-line not-rely-on-time */\\n optimisticMintingDelayUpdateInitiatedTimestamp = block.timestamp;\\n newOptimisticMintingDelay = _newOptimisticMintingDelay;\\n emit OptimisticMintingDelayUpdateStarted(_newOptimisticMintingDelay);\\n }\\n\\n /// @notice Finalizes the update process of the optimistic minting delay.\\n function finalizeOptimisticMintingDelayUpdate()\\n external\\n onlyOwner\\n onlyAfterGovernanceDelay(optimisticMintingDelayUpdateInitiatedTimestamp)\\n {\\n optimisticMintingDelay = newOptimisticMintingDelay;\\n emit OptimisticMintingDelayUpdated(newOptimisticMintingDelay);\\n\\n newOptimisticMintingDelay = 0;\\n optimisticMintingDelayUpdateInitiatedTimestamp = 0;\\n }\\n\\n /// @notice Calculates deposit key the same way as the Bridge contract.\\n /// The deposit key is computed as\\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\\n function calculateDepositKey(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) public pure returns (uint256) {\\n return\\n uint256(\\n keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex))\\n );\\n }\\n\\n /// @notice Used by `TBTCVault.receiveBalanceIncrease` to repay the optimistic\\n /// minting debt before TBTC is minted. When optimistic minting is\\n /// finalized, debt equal to the value of the deposit being\\n /// a subject of the optimistic minting is incurred. When `TBTCVault`\\n /// sweeps a deposit, the debt is fully or partially paid off, no\\n /// matter if that particular deposit was used for the optimistic\\n /// minting or not.\\n /// @dev See `TBTCVault.receiveBalanceIncrease`\\n /// @param depositor The depositor whose balance increase is received.\\n /// @param amount The balance increase amount for the depositor received.\\n /// @return The TBTC amount that should be minted after paying off the\\n /// optimistic minting debt.\\n function repayOptimisticMintingDebt(address depositor, uint256 amount)\\n internal\\n returns (uint256)\\n {\\n uint256 debt = optimisticMintingDebt[depositor];\\n if (debt == 0) {\\n return amount;\\n }\\n\\n if (amount > debt) {\\n optimisticMintingDebt[depositor] = 0;\\n emit OptimisticMintingDebtRepaid(depositor, 0);\\n return amount - debt;\\n } else {\\n optimisticMintingDebt[depositor] = debt - amount;\\n emit OptimisticMintingDebtRepaid(depositor, debt - amount);\\n return 0;\\n }\\n }\\n}\\n\",\"keccak256\":\"0xa4bfc703dafeca2acb0e3c5c96d7f052692147b3812496f426f43e25ef4ba123\",\"license\":\"GPL-3.0-only\"},\"contracts/vault/TBTCVault.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IVault.sol\\\";\\nimport \\\"./TBTCOptimisticMinting.sol\\\";\\nimport \\\"../bank/Bank.sol\\\";\\nimport \\\"../token/TBTC.sol\\\";\\n\\n/// @title TBTC application vault\\n/// @notice TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of\\n/// Bitcoin. It facilitates Bitcoin holders to act on the Ethereum\\n/// blockchain and access the decentralized finance (DeFi) ecosystem.\\n/// TBTC Vault mints and unmints TBTC based on Bitcoin balances in the\\n/// Bank.\\n/// @dev TBTC Vault is the owner of TBTC token contract and is the only contract\\n/// minting the token.\\ncontract TBTCVault is IVault, Ownable, TBTCOptimisticMinting {\\n using SafeERC20 for IERC20;\\n\\n Bank public immutable bank;\\n TBTC public immutable tbtcToken;\\n\\n /// @notice The address of a new TBTC vault. Set only when the upgrade\\n /// process is pending. Once the upgrade gets finalized, the new\\n /// TBTC vault will become an owner of TBTC token.\\n address public newVault;\\n /// @notice The timestamp at which an upgrade to a new TBTC vault was\\n /// initiated. Set only when the upgrade process is pending.\\n uint256 public upgradeInitiatedTimestamp;\\n\\n event Minted(address indexed to, uint256 amount);\\n event Unminted(address indexed from, uint256 amount);\\n\\n event UpgradeInitiated(address newVault, uint256 timestamp);\\n event UpgradeFinalized(address newVault);\\n\\n modifier onlyBank() {\\n require(msg.sender == address(bank), \\\"Caller is not the Bank\\\");\\n _;\\n }\\n\\n constructor(\\n Bank _bank,\\n TBTC _tbtcToken,\\n Bridge _bridge\\n ) TBTCOptimisticMinting(_bridge) {\\n require(\\n address(_bank) != address(0),\\n \\\"Bank can not be the zero address\\\"\\n );\\n\\n require(\\n address(_tbtcToken) != address(0),\\n \\\"TBTC token can not be the zero address\\\"\\n );\\n\\n bank = _bank;\\n tbtcToken = _tbtcToken;\\n }\\n\\n /// @notice Mints the given `amount` of TBTC to the caller previously\\n /// transferring `amount / SATOSHI_MULTIPLIER` of the Bank balance\\n /// from caller to TBTC Vault. If `amount` is not divisible by\\n /// SATOSHI_MULTIPLIER, the remainder is left on the caller's\\n /// Bank balance.\\n /// @dev TBTC Vault must have an allowance for caller's balance in the\\n /// Bank for at least `amount / SATOSHI_MULTIPLIER`.\\n /// @param amount Amount of TBTC to mint.\\n function mint(uint256 amount) external {\\n (uint256 convertibleAmount, , uint256 satoshis) = amountToSatoshis(\\n amount\\n );\\n\\n require(\\n bank.balanceOf(msg.sender) >= satoshis,\\n \\\"Amount exceeds balance in the bank\\\"\\n );\\n _mint(msg.sender, convertibleAmount);\\n bank.transferBalanceFrom(msg.sender, address(this), satoshis);\\n }\\n\\n /// @notice Transfers `satoshis` of the Bank balance from the caller\\n /// to TBTC Vault and mints `satoshis * SATOSHI_MULTIPLIER` of TBTC\\n /// to the caller.\\n /// @dev Can only be called by the Bank via `approveBalanceAndCall`.\\n /// @param owner The owner who approved their Bank balance.\\n /// @param satoshis Amount of satoshis used to mint TBTC.\\n function receiveBalanceApproval(\\n address owner,\\n uint256 satoshis,\\n bytes calldata\\n ) external override onlyBank {\\n require(\\n bank.balanceOf(owner) >= satoshis,\\n \\\"Amount exceeds balance in the bank\\\"\\n );\\n _mint(owner, satoshis * SATOSHI_MULTIPLIER);\\n bank.transferBalanceFrom(owner, address(this), satoshis);\\n }\\n\\n /// @notice Mints the same amount of TBTC as the deposited satoshis amount\\n /// multiplied by SATOSHI_MULTIPLIER for each depositor in the array.\\n /// Can only be called by the Bank after the Bridge swept deposits\\n /// and Bank increased balance for the vault.\\n /// @dev Fails if `depositors` array is empty. Expects the length of\\n /// `depositors` and `depositedSatoshiAmounts` is the same.\\n function receiveBalanceIncrease(\\n address[] calldata depositors,\\n uint256[] calldata depositedSatoshiAmounts\\n ) external override onlyBank {\\n require(depositors.length != 0, \\\"No depositors specified\\\");\\n for (uint256 i = 0; i < depositors.length; i++) {\\n address depositor = depositors[i];\\n uint256 satoshis = depositedSatoshiAmounts[i];\\n _mint(\\n depositor,\\n repayOptimisticMintingDebt(\\n depositor,\\n satoshis * SATOSHI_MULTIPLIER\\n )\\n );\\n }\\n }\\n\\n /// @notice Burns `amount` of TBTC from the caller's balance and transfers\\n /// `amount / SATOSHI_MULTIPLIER` back to the caller's balance in\\n /// the Bank. If `amount` is not divisible by SATOSHI_MULTIPLIER,\\n /// the remainder is left on the caller's account.\\n /// @dev Caller must have at least `amount` of TBTC approved to\\n /// TBTC Vault.\\n /// @param amount Amount of TBTC to unmint.\\n function unmint(uint256 amount) external {\\n (uint256 convertibleAmount, , ) = amountToSatoshis(amount);\\n\\n _unmint(msg.sender, convertibleAmount);\\n }\\n\\n /// @notice Burns `amount` of TBTC from the caller's balance and transfers\\n /// `amount / SATOSHI_MULTIPLIER` of Bank balance to the Bridge\\n /// requesting redemption based on the provided `redemptionData`.\\n /// If `amount` is not divisible by SATOSHI_MULTIPLIER, the\\n /// remainder is left on the caller's account.\\n /// @dev Caller must have at least `amount` of TBTC approved to\\n /// TBTC Vault.\\n /// @param amount Amount of TBTC to unmint and request to redeem in Bridge.\\n /// @param redemptionData Redemption data in a format expected from\\n /// `redemptionData` parameter of Bridge's `receiveBalanceApproval`\\n /// function.\\n function unmintAndRedeem(uint256 amount, bytes calldata redemptionData)\\n external\\n {\\n (uint256 convertibleAmount, , ) = amountToSatoshis(amount);\\n\\n _unmintAndRedeem(msg.sender, convertibleAmount, redemptionData);\\n }\\n\\n /// @notice Burns `amount` of TBTC from the caller's balance. If `extraData`\\n /// is empty, transfers `amount` back to the caller's balance in the\\n /// Bank. If `extraData` is not empty, requests redemption in the\\n /// Bridge using the `extraData` as a `redemptionData` parameter to\\n /// Bridge's `receiveBalanceApproval` function.\\n /// If `amount` is not divisible by SATOSHI_MULTIPLIER, the\\n /// remainder is left on the caller's account. Note that it may\\n /// left a token approval equal to the remainder.\\n /// @dev This function is doing the same as `unmint` or `unmintAndRedeem`\\n /// (depending on `extraData` parameter) but it allows to execute\\n /// unminting without a separate approval transaction. The function can\\n /// be called only via `approveAndCall` of TBTC token.\\n /// @param from TBTC token holder executing unminting.\\n /// @param amount Amount of TBTC to unmint.\\n /// @param token TBTC token address.\\n /// @param extraData Redemption data in a format expected from\\n /// `redemptionData` parameter of Bridge's `receiveBalanceApproval`\\n /// function. If empty, `receiveApproval` is not requesting a\\n /// redemption of Bank balance but is instead performing just TBTC\\n /// unminting to a Bank balance.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external {\\n require(token == address(tbtcToken), \\\"Token is not TBTC\\\");\\n require(msg.sender == token, \\\"Only TBTC caller allowed\\\");\\n (uint256 convertibleAmount, , ) = amountToSatoshis(amount);\\n if (extraData.length == 0) {\\n _unmint(from, convertibleAmount);\\n } else {\\n _unmintAndRedeem(from, convertibleAmount, extraData);\\n }\\n }\\n\\n /// @notice Initiates vault upgrade process. The upgrade process needs to be\\n /// finalized with a call to `finalizeUpgrade` function after the\\n /// `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can\\n /// initiate the upgrade.\\n /// @param _newVault The new vault address.\\n function initiateUpgrade(address _newVault) external onlyOwner {\\n require(_newVault != address(0), \\\"New vault address cannot be zero\\\");\\n /* solhint-disable-next-line not-rely-on-time */\\n emit UpgradeInitiated(_newVault, block.timestamp);\\n /* solhint-disable-next-line not-rely-on-time */\\n upgradeInitiatedTimestamp = block.timestamp;\\n newVault = _newVault;\\n }\\n\\n /// @notice Allows the governance to finalize vault upgrade process. The\\n /// upgrade process needs to be first initiated with a call to\\n /// `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass.\\n /// Once the upgrade is finalized, the new vault becomes the owner\\n /// of the TBTC token and receives the whole Bank balance of this\\n /// vault.\\n function finalizeUpgrade()\\n external\\n onlyOwner\\n onlyAfterGovernanceDelay(upgradeInitiatedTimestamp)\\n {\\n emit UpgradeFinalized(newVault);\\n // slither-disable-next-line reentrancy-no-eth\\n tbtcToken.transferOwnership(newVault);\\n bank.transferBalance(newVault, bank.balanceOf(address(this)));\\n newVault = address(0);\\n upgradeInitiatedTimestamp = 0;\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC20\\n /// token sent mistakenly to the TBTC token contract address.\\n /// @param token Address of the recovered ERC20 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param amount Recovered amount.\\n function recoverERC20FromToken(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n tbtcToken.recoverERC20(token, recipient, amount);\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC721\\n /// token sent mistakenly to the TBTC token contract address.\\n /// @param token Address of the recovered ERC721 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param tokenId Identifier of the recovered token.\\n /// @param data Additional data.\\n function recoverERC721FromToken(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n tbtcToken.recoverERC721(token, recipient, tokenId, data);\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC20\\n /// token sent - mistakenly or not - to the vault address. This\\n /// function should be used to withdraw TBTC v1 tokens transferred\\n /// to TBTCVault as a result of VendingMachine > TBTCVault upgrade.\\n /// @param token Address of the recovered ERC20 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param amount Recovered amount.\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC721\\n /// token sent mistakenly to the vault address.\\n /// @param token Address of the recovered ERC721 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param tokenId Identifier of the recovered token.\\n /// @param data Additional data.\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n\\n /// @notice Returns the amount of TBTC to be minted/unminted, the remainder,\\n /// and the Bank balance to be transferred for the given mint/unmint.\\n /// Note that if the `amount` is not divisible by SATOSHI_MULTIPLIER,\\n /// the remainder is left on the caller's account when minting or\\n /// unminting.\\n /// @return convertibleAmount Amount of TBTC to be minted/unminted.\\n /// @return remainder Not convertible remainder if amount is not divisible\\n /// by SATOSHI_MULTIPLIER.\\n /// @return satoshis Amount in satoshis - the Bank balance to be transferred\\n /// for the given mint/unmint\\n function amountToSatoshis(uint256 amount)\\n public\\n view\\n returns (\\n uint256 convertibleAmount,\\n uint256 remainder,\\n uint256 satoshis\\n )\\n {\\n remainder = amount % SATOSHI_MULTIPLIER;\\n convertibleAmount = amount - remainder;\\n satoshis = convertibleAmount / SATOSHI_MULTIPLIER;\\n }\\n\\n // slither-disable-next-line calls-loop\\n function _mint(address minter, uint256 amount) internal override {\\n emit Minted(minter, amount);\\n tbtcToken.mint(minter, amount);\\n }\\n\\n /// @dev `amount` MUST be divisible by SATOSHI_MULTIPLIER with no change.\\n function _unmint(address unminter, uint256 amount) internal {\\n emit Unminted(unminter, amount);\\n tbtcToken.burnFrom(unminter, amount);\\n bank.transferBalance(unminter, amount / SATOSHI_MULTIPLIER);\\n }\\n\\n /// @dev `amount` MUST be divisible by SATOSHI_MULTIPLIER with no change.\\n function _unmintAndRedeem(\\n address redeemer,\\n uint256 amount,\\n bytes calldata redemptionData\\n ) internal {\\n emit Unminted(redeemer, amount);\\n tbtcToken.burnFrom(redeemer, amount);\\n bank.approveBalanceAndCall(\\n address(bridge),\\n amount / SATOSHI_MULTIPLIER,\\n redemptionData\\n );\\n }\\n}\\n\",\"keccak256\":\"0x98f5c27ce01926f53eb30daa11bd9b2b0cf24274d81a99a04a74ed220f0a69cb\",\"license\":\"GPL-3.0-only\"}},\"version\":1}", + "bytecode": "0x60e060405260008054600160a81b600160e81b031916650a8c0000007d60aa1b1790553480156200002f57600080fd5b5060405162003e2f38038062003e2f83398101604081905262000052916200020c565b806200005e33620001a3565b6001600160a01b038116620000c55760405162461bcd60e51b815260206004820152602260248201527f4272696467652063616e206e6f7420626520746865207a65726f206164647265604482015261737360f01b60648201526084015b60405180910390fd5b6001600160a01b039081166080528316620001235760405162461bcd60e51b815260206004820181905260248201527f42616e6b2063616e206e6f7420626520746865207a65726f20616464726573736044820152606401620000bc565b6001600160a01b0382166200018a5760405162461bcd60e51b815260206004820152602660248201527f5442544320746f6b656e2063616e206e6f7420626520746865207a65726f206160448201526564647265737360d01b6064820152608401620000bc565b506001600160a01b0391821660a0521660c05262000260565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146200020957600080fd5b50565b6000806000606084860312156200022257600080fd5b83516200022f81620001f3565b60208501519093506200024281620001f3565b60408501519092506200025581620001f3565b809150509250925092565b60805160a05160c051613b136200031c600039600081816107330152818161098001528181610cf401528181611fb0015281816123b101528181612d6d01528181612ed8015261301f01526000818161057901528181610d6101528181610ebc01528181610f4a0152818161104f0152818161242d015281816125650152818161265601528181612f36015261307d01526000818161075a015281816115720152818161170e01528181611d620152612f650152613b136000f3fe608060405234801561001057600080fd5b50600436106103365760003560e01c80638532c511116101b2578063a410d29b116100f9578063c36b32b7116100a2578063e5d3d7141161007c578063e5d3d7141461072e578063e78cea9214610755578063f2fde38b1461077c578063fc4e51f61461078f57600080fd5b8063c36b32b7146106ff578063c7ba034714610712578063d0ff65b51461071e57600080fd5b8063b0b54895116100d3578063b0b54895146106cd578063b9f78798146106d7578063bc7b6b99146106f757600080fd5b8063a410d29b1461068f578063a526d83b14610697578063aa271e1a146106aa57600080fd5b8063951315261161015b5780639a508c8e116101355780639a508c8e1461066b578063a0712d6814610673578063a0cceb951461068657600080fd5b80639513152614610647578063983b2d56146106505780639a4e36d51461066357600080fd5b8063897f712c1161018c578063897f712c1461060f5780638da5cb5b146106235780638f4ffcb11461063457600080fd5b80638532c511146105d95780638623ec7b146105e957806388aaf0c8146105fc57600080fd5b806347c1ffdb116102815780636c626aa41161022a5780637445a5a0116102045780637445a5a01461054657806376cdb03b1461057457806380df5ed2146105b3578063820b5513146105c657600080fd5b80636c626aa4146104d0578063714041561461052b578063715018a61461053e57600080fd5b806364e779b11161025b57806364e779b1146104955780636abe3a6c146104a85780636b32810b146104bb57600080fd5b806347c1ffdb1461046357806353dce4df1461046b5780635ae2da461461047e57600080fd5b80633092afd5116102e3578063461c6373116102bd578063461c63731461042a578063475d05701461043d578063479aa9271461045057600080fd5b80633092afd5146103fb578063317dfa761461040e57806341906ab71461042157600080fd5b80631171bda9116103145780631171bda9146103b4578063124f65bd146103c75780632e73e398146103e857600080fd5b806309b53f511461033b5780630c68ba211461036c5780630f3425731461039f575b600080fd5b60005461035290600160a81b900463ffffffff1681565b60405163ffffffff90911681526020015b60405180910390f35b61038f61037a3660046133e9565b60036020526000908152604090205460ff1681565b6040519015158152602001610363565b6103b26103ad366004613418565b6107a2565b005b6103b26103c2366004613435565b610842565b6103da6103d5366004613476565b6108a3565b604051908152602001610363565b6103b26103f63660046134ef565b610908565b6103b26104093660046133e9565b6109f6565b6103b261041c366004613435565b610c65565b6103da60095481565b6103b26104383660046135a7565b610d56565b6103b261044b366004613613565b610eb1565b6103b261045e3660046133e9565b6110b2565b6103b26111b7565b6103b2610479366004613663565b611295565b60005461035290600160c81b900463ffffffff1681565b6103b26104a33660046136af565b6112b6565b6103b26104b6366004613476565b6112d3565b6104c3611820565b60405161036391906136c8565b61050a6104de3660046136af565b60046020526000908152604090205467ffffffffffffffff808216916801000000000000000090041682565b6040805167ffffffffffffffff938416815292909116602083015201610363565b6103b26105393660046133e9565b611882565b6103b261197b565b6105596105543660046136af565b6119cf565b60408051938452602084019290925290820152606001610363565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610363565b6103b26105c1366004613476565b611a07565b6103b26105d4366004613476565b611bf3565b6006546103529063ffffffff1681565b61059b6105f73660046136af565b611f84565b600a5461059b906001600160a01b031681565b60005461038f90600160a01b900460ff1681565b6000546001600160a01b031661059b565b6103b2610642366004613715565b611fae565b6103da600b5481565b6103b261065e3660046133e9565b6120c3565b6103b2612200565b6103b26122de565b6103b26106813660046136af565b612535565b6103da60075481565b6103b261268d565b6103b26106a53660046133e9565b612775565b61038f6106b83660046133e9565b60016020526000908152604090205460ff1681565b6103da6201518081565b6103da6106e53660046133e9565b60056020526000908152604090205481565b6103b2612898565b6103b261070d366004613418565b61296f565b6103da6402540be40081565b6008546103529063ffffffff1681565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6103b261078a3660046133e9565b612a03565b6103b261079d3660046134ef565b612ad3565b6000546001600160a01b031633146107ef5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064015b60405180910390fd5b426009556008805463ffffffff191663ffffffff83169081179091556040519081527f682bc0fb7e0d6bcb974cf556b95f68533cafc411d83d9f33ac192ccf45dda605906020015b60405180910390a150565b6000546001600160a01b0316331461088a5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b61089e6001600160a01b0384168383612b68565b505050565b600082826040516020016108e692919091825260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016602082015260240190565b6040516020818303038152906040528051906020012060001c90505b92915050565b6000546001600160a01b031633146109505760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517ffc4e51f60000000000000000000000000000000000000000000000000000000081526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063fc4e51f6906109bd9088908890889088908890600401613794565b600060405180830381600087803b1580156109d757600080fd5b505af11580156109eb573d6000803e3d6000fd5b505050505050505050565b33610a096000546001600160a01b031690565b6001600160a01b03161480610a2d57503360009081526003602052604090205460ff165b610a9f5760405162461bcd60e51b815260206004820152602360248201527f43616c6c6572206973206e6f7420746865206f776e6572206f7220677561726460448201527f69616e000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b03811660009081526001602052604090205460ff16610b075760405162461bcd60e51b815260206004820152601c60248201527f546869732061646472657373206973206e6f742061206d696e7465720000000060448201526064016107e6565b6001600160a01b0381166000908152600160205260408120805460ff191690555b600254811015610c2d57816001600160a01b031660028281548110610b4f57610b4f6137c7565b6000918252602090912001546001600160a01b031603610c1b5760028054610b79906001906137f3565b81548110610b8957610b896137c7565b600091825260209091200154600280546001600160a01b039092169183908110610bb557610bb56137c7565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506002805480610bf457610bf4613806565b600082815260209020810160001990810180546001600160a01b0319169055019055610c2d565b80610c258161381c565b915050610b28565b506040516001600160a01b038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000546001600160a01b03163314610cad5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517f1171bda90000000000000000000000000000000000000000000000000000000081526001600160a01b0384811660048301528381166024830152604482018390527f00000000000000000000000000000000000000000000000000000000000000001690631171bda9906064015b600060405180830381600087803b158015610d3957600080fd5b505af1158015610d4d573d6000803e3d6000fd5b50505050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610dce5760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6000839003610e1f5760405162461bcd60e51b815260206004820152601760248201527f4e6f206465706f7369746f72732073706563696669656400000000000000000060448201526064016107e6565b60005b83811015610eaa576000858583818110610e3e57610e3e6137c7565b9050602002016020810190610e5391906133e9565b90506000848484818110610e6957610e696137c7565b905060200201359050610e9582610e90846402540be40085610e8b9190613835565b612be8565b612ceb565b50508080610ea29061381c565b915050610e22565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f295760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6040516370a0823160e01b81526001600160a01b03858116600483015284917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa158015610f93573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb7919061384c565b10156110105760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b61102384610e906402540be40086613835565b604051631f1b6d2760e21b81526001600160a01b038581166004830152306024830152604482018590527f00000000000000000000000000000000000000000000000000000000000000001690637c6db49c906064015b600060405180830381600087803b15801561109457600080fd5b505af11580156110a8573d6000803e3d6000fd5b5050505050505050565b6000546001600160a01b031633146110fa5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b0381166111505760405162461bcd60e51b815260206004820181905260248201527f4e6577207661756c7420616464726573732063616e6e6f74206265207a65726f60448201526064016107e6565b604080516001600160a01b03831681524260208201527f5cc842cab066489e13292128663547c68705dbf476f0131e0107f155719c6124910160405180910390a142600b55600a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146111ff5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b60075461120f8162015180612dc6565b600654600080547fffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160a81b81029290921790556040519081527fa7f4ce7c3586e2000cdec6b25c5e7d0b20f9b4f435aa22d9c1feb32dbb506f779060200160405180910390a1506006805463ffffffff191690556000600755565b60006112a0846119cf565b505090506112b033828585612e6f565b50505050565b60006112c1826119cf565b505090506112cf3382612fb6565b5050565b3360009081526001602052604090205460ff166113325760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff161561138c5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b600061139883836108a3565b600081815260046020526040812080549293509167ffffffffffffffff16900361142a5760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff16156114b55760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60005481546114db91600160c81b900463ffffffff169067ffffffffffffffff16613865565b67ffffffffffffffff1642116115595760405162461bcd60e51b815260206004820152602b60248201527f4f7074696d6973746963206d696e74696e672064656c617920686173206e6f7460448201527f207061737365642079657400000000000000000000000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa1580156115c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115e591906138b9565b90508060a0015163ffffffff166000146116415760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60006402540be4008260800151836020015161165d9190613966565b67ffffffffffffffff166116719190613835565b6000805491925090600160a81b900463ffffffff166116915760006116ab565b6000546116ab90600160a81b900463ffffffff168361399d565b83516001600160a01b0316600090815260056020526040812054919250906116d49084906139b1565b84516001600160a01b03166000908152600560205260409020819055845190915061170390610e9084866137f3565b8115611794576117947f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166361d027b36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561176a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061178e91906139c4565b83612ceb565b84547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16680100000000000000004267ffffffffffffffff160217855583516040518281526001600160a01b0390911690879033907f2cffebf26d639426e79514d100febae8b2c63e700e5dc0fa6c88a129633506369060200160405180910390a45050505050505050565b6060600280548060200260200160405190810160405280929190818152602001828054801561187857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161185a575b5050505050905090565b6000546001600160a01b031633146118ca5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff166119325760405162461bcd60e51b815260206004820152601e60248201527f546869732061646472657373206973206e6f74206120677561726469616e000060448201526064016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19169055517fb8107d0c6b40be480ce3172ee66ba6d64b71f6b1685a851340036e6e2e3e3c529190a250565b6000546001600160a01b031633146119c35760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6119cd6000613105565b565b600080806119e26402540be400856139e1565b91506119ee82856137f3565b92506119ff6402540be4008461399d565b929491935050565b3360009081526003602052604090205460ff16611a665760405162461bcd60e51b815260206004820152601860248201527f43616c6c6572206973206e6f74206120677561726469616e000000000000000060448201526064016107e6565b6000611a7283836108a3565b600081815260046020526040812080549293509167ffffffffffffffff169003611b045760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff1615611b8f5760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60008281526004602052604080822080547fffffffffffffffffffffffffffffffff0000000000000000000000000000000016905551839133917f1256b41d4b18d922811c358ab80cb0375aae28f45373de35cfda580662193fcd9190a350505050565b3360009081526001602052604090205460ff16611c525760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff1615611cac5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b6000611cb883836108a3565b600081815260046020526040902080549192509067ffffffffffffffff1615611d495760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c726561647920726571756560448201527f7374656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa158015611db1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dd591906138b9565b9050806040015163ffffffff16600003611e3b5760405162461bcd60e51b815260206004820152602160248201527f546865206465706f73697420686173206e6f74206265656e2072657665616c656044820152601960fa1b60648201526084016107e6565b60a081015163ffffffff1615611e935760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60608101516001600160a01b03163014611eef5760405162461bcd60e51b815260206004820152601860248201527f556e6578706563746564207661756c742061646472657373000000000000000060448201526064016107e6565b815467ffffffffffffffff19164267ffffffffffffffff908116919091178355815160208301516001600160a01b0390911691859133917f36f39c606d55d7dd2a05b8c4e41e9a6ca8c501cea10009c1762f6826a146e05591611f59916402540be4009116613835565b60408051918252602082018b905263ffffffff8a169082015260600160405180910390a45050505050565b60028181548110611f9457600080fd5b6000918252602090912001546001600160a01b0316905081565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161461202f5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e206973206e6f74205442544300000000000000000000000000000060448201526064016107e6565b336001600160a01b038416146120875760405162461bcd60e51b815260206004820152601860248201527f4f6e6c7920544254432063616c6c657220616c6c6f776564000000000000000060448201526064016107e6565b6000612092856119cf565b509091505060008290036120af576120aa8682612fb6565b6120bb565b6120bb86828585612e6f565b505050505050565b6000546001600160a01b0316331461210b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526001602052604090205460ff16156121745760405162461bcd60e51b815260206004820181905260248201527f54686973206164647265737320697320616c72656164792061206d696e74657260448201526064016107e6565b6001600160a01b0381166000818152600160208190526040808320805460ff19168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180546001600160a01b03191684179055517f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f69190a250565b6000546001600160a01b031633146122485760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6009546122588162015180612dc6565b600854600080547fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160c81b81029290921790556040519081527ff52de3377e3ae270d1e38f99b9b8d587814643811516ea55ba4d597f9950d4ec9060200160405180910390a1506008805463ffffffff191690556000600955565b6000546001600160a01b031633146123265760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600b546123368162015180612dc6565b600a546040516001600160a01b0390911681527f81a9bb8030ed4116b405800280e065110a37afb57b69948e714c97fab23475ec9060200160405180910390a1600a546040517ff2fde38b0000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201527f00000000000000000000000000000000000000000000000000000000000000009091169063f2fde38b90602401600060405180830381600087803b1580156123f757600080fd5b505af115801561240b573d6000803e3d6000fd5b5050600a546040516370a0823160e01b81523060048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811694506356a6d9ef93509091169083906370a0823190602401602060405180830381865afa158015612483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124a7919061384c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561250557600080fd5b505af1158015612519573d6000803e3d6000fd5b5050600a80546001600160a01b031916905550506000600b5550565b600080612541836119cf565b6040516370a0823160e01b8152336004820152929450925082916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691506370a0823190602401602060405180830381865afa1580156125ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d1919061384c565b101561262a5760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b6126343383612ceb565b604051631f1b6d2760e21b8152336004820152306024820152604481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690637c6db49c90606401610d1f565b6000546001600160a01b031633146126d55760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff16156127395760405162461bcd60e51b815260206004820152602160248201527f4f7074696d6973746963206d696e74696e6720616c72656164792070617573656044820152601960fa1b60648201526084016107e6565b6000805460ff60a01b1916600160a01b1781556040517f23a83c8aeda8c831401c17b5bfb8b2ead79fcfe9c027fe34a4f8576e2c7c74cc9190a1565b6000546001600160a01b031633146127bd5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff161561284c5760405162461bcd60e51b815260206004820152602260248201527f54686973206164647265737320697320616c726561647920612067756172646960448201527f616e00000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19166001179055517f038596bb31e2e7d3d9f184d4c98b310103f6d7f5830e5eec32bffe6f1728f9699190a250565b6000546001600160a01b031633146128e05760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff166129395760405162461bcd60e51b815260206004820181905260248201527f4f7074696d6973746963206d696e74696e67206973206e6f742070617573656460448201526064016107e6565b6000805460ff60a01b191681556040517fcb27470ed9568d9eeb8939707bafc19404d908a26ce5f468a6aa781024fd6a839190a1565b6000546001600160a01b031633146129b75760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b426007556006805463ffffffff191663ffffffff83169081179091556040519081527f0dbfec7f12acffbb5cec595ac4370907eaf84caa7025dd71f4021433be79eba990602001610837565b6000546001600160a01b03163314612a4b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b038116612ac75760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016107e6565b612ad081613105565b50565b6000546001600160a01b03163314612b1b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde906109bd9030908890889088908890600401613794565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261089e908490613155565b6001600160a01b038216600090815260056020526040812054808203612c115782915050610902565b80831115612c78576001600160a01b0384166000818152600560209081526040808320839055519182527fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8910160405180910390a2612c7081846137f3565b915050610902565b612c8283826137f3565b6001600160a01b0385166000818152600560205260409020919091557fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8612cc985846137f3565b60405190815260200160405180910390a26000915050610902565b5092915050565b816001600160a01b03167f30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe82604051612d2691815260200190565b60405180910390a26040517f40c10f190000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906340c10f19906044015b600060405180830381600087803b158015612db257600080fd5b505af11580156120bb573d6000803e3d6000fd5b60008211612e165760405162461bcd60e51b815260206004820152601460248201527f4368616e6765206e6f7420696e6974696174656400000000000000000000000060448201526064016107e6565b80612e2183426137f3565b10156112cf5760405162461bcd60e51b815260206004820181905260248201527f476f7665726e616e63652064656c617920686173206e6f7420656c617073656460448201526064016107e6565b836001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b884604051612eaa91815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038581166004830152602482018590527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b158015612f1c57600080fd5b505af1158015612f30573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634a38757e7f00000000000000000000000000000000000000000000000000000000000000006402540be40086612f95919061399d565b85856040518563ffffffff1660e01b815260040161107a94939291906139f5565b816001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b882604051612ff191815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b15801561306357600080fd5b505af1158015613077573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356a6d9ef836402540be400846130bc919061399d565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401612d98565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006131aa826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661323a9092919063ffffffff16565b80519091501561089e57808060200190518101906131c89190613a28565b61089e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016107e6565b60606132498484600085613253565b90505b9392505050565b6060824710156132cb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b0385163b6133225760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107e6565b600080866001600160a01b0316858760405161333e9190613a6e565b60006040518083038185875af1925050503d806000811461337b576040519150601f19603f3d011682016040523d82523d6000602084013e613380565b606091505b509150915061339082828661339b565b979650505050505050565b606083156133aa57508161324c565b8251156133ba5782518084602001fd5b8160405162461bcd60e51b81526004016107e69190613a8a565b6001600160a01b0381168114612ad057600080fd5b6000602082840312156133fb57600080fd5b813561324c816133d4565b63ffffffff81168114612ad057600080fd5b60006020828403121561342a57600080fd5b813561324c81613406565b60008060006060848603121561344a57600080fd5b8335613455816133d4565b92506020840135613465816133d4565b929592945050506040919091013590565b6000806040838503121561348957600080fd5b82359150602083013561349b81613406565b809150509250929050565b60008083601f8401126134b857600080fd5b50813567ffffffffffffffff8111156134d057600080fd5b6020830191508360208285010111156134e857600080fd5b9250929050565b60008060008060006080868803121561350757600080fd5b8535613512816133d4565b94506020860135613522816133d4565b935060408601359250606086013567ffffffffffffffff81111561354557600080fd5b613551888289016134a6565b969995985093965092949392505050565b60008083601f84011261357457600080fd5b50813567ffffffffffffffff81111561358c57600080fd5b6020830191508360208260051b85010111156134e857600080fd5b600080600080604085870312156135bd57600080fd5b843567ffffffffffffffff808211156135d557600080fd5b6135e188838901613562565b909650945060208701359150808211156135fa57600080fd5b5061360787828801613562565b95989497509550505050565b6000806000806060858703121561362957600080fd5b8435613634816133d4565b935060208501359250604085013567ffffffffffffffff81111561365757600080fd5b613607878288016134a6565b60008060006040848603121561367857600080fd5b83359250602084013567ffffffffffffffff81111561369657600080fd5b6136a2868287016134a6565b9497909650939450505050565b6000602082840312156136c157600080fd5b5035919050565b6020808252825182820181905260009190848201906040850190845b818110156137095783516001600160a01b0316835292840192918401916001016136e4565b50909695505050505050565b60008060008060006080868803121561372d57600080fd5b8535613738816133d4565b945060208601359350604086013561374f816133d4565b9250606086013567ffffffffffffffff81111561354557600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60006001600160a01b0380881683528087166020840152508460408301526080606083015261339060808301848661376b565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115610902576109026137dd565b634e487b7160e01b600052603160045260246000fd5b60006001820161382e5761382e6137dd565b5060010190565b8082028115828204841417610902576109026137dd565b60006020828403121561385e57600080fd5b5051919050565b67ffffffffffffffff818116838216019080821115612ce457612ce46137dd565b8051613891816133d4565b919050565b805167ffffffffffffffff8116811461389157600080fd5b805161389181613406565b600060c082840312156138cb57600080fd5b60405160c0810181811067ffffffffffffffff821117156138fc57634e487b7160e01b600052604160045260246000fd5b60405261390883613886565b815261391660208401613896565b6020820152613927604084016138ae565b604082015261393860608401613886565b606082015261394960808401613896565b608082015261395a60a084016138ae565b60a08201529392505050565b67ffffffffffffffff828116828216039080821115612ce457612ce46137dd565b634e487b7160e01b600052601260045260246000fd5b6000826139ac576139ac613987565b500490565b80820180821115610902576109026137dd565b6000602082840312156139d657600080fd5b815161324c816133d4565b6000826139f0576139f0613987565b500690565b6001600160a01b0385168152836020820152606060408201526000613a1e60608301848661376b565b9695505050505050565b600060208284031215613a3a57600080fd5b8151801515811461324c57600080fd5b60005b83811015613a65578181015183820152602001613a4d565b50506000910152565b60008251613a80818460208701613a4a565b9190910192915050565b6020815260008251806020840152613aa9816040850160208701613a4a565b601f01601f1916919091016040019291505056fe4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572a2646970667358221220aabd28c13405d8cf2c5332bb69dcd504bd839194f1d43914aef46237f5af30d464736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106103365760003560e01c80638532c511116101b2578063a410d29b116100f9578063c36b32b7116100a2578063e5d3d7141161007c578063e5d3d7141461072e578063e78cea9214610755578063f2fde38b1461077c578063fc4e51f61461078f57600080fd5b8063c36b32b7146106ff578063c7ba034714610712578063d0ff65b51461071e57600080fd5b8063b0b54895116100d3578063b0b54895146106cd578063b9f78798146106d7578063bc7b6b99146106f757600080fd5b8063a410d29b1461068f578063a526d83b14610697578063aa271e1a146106aa57600080fd5b8063951315261161015b5780639a508c8e116101355780639a508c8e1461066b578063a0712d6814610673578063a0cceb951461068657600080fd5b80639513152614610647578063983b2d56146106505780639a4e36d51461066357600080fd5b8063897f712c1161018c578063897f712c1461060f5780638da5cb5b146106235780638f4ffcb11461063457600080fd5b80638532c511146105d95780638623ec7b146105e957806388aaf0c8146105fc57600080fd5b806347c1ffdb116102815780636c626aa41161022a5780637445a5a0116102045780637445a5a01461054657806376cdb03b1461057457806380df5ed2146105b3578063820b5513146105c657600080fd5b80636c626aa4146104d0578063714041561461052b578063715018a61461053e57600080fd5b806364e779b11161025b57806364e779b1146104955780636abe3a6c146104a85780636b32810b146104bb57600080fd5b806347c1ffdb1461046357806353dce4df1461046b5780635ae2da461461047e57600080fd5b80633092afd5116102e3578063461c6373116102bd578063461c63731461042a578063475d05701461043d578063479aa9271461045057600080fd5b80633092afd5146103fb578063317dfa761461040e57806341906ab71461042157600080fd5b80631171bda9116103145780631171bda9146103b4578063124f65bd146103c75780632e73e398146103e857600080fd5b806309b53f511461033b5780630c68ba211461036c5780630f3425731461039f575b600080fd5b60005461035290600160a81b900463ffffffff1681565b60405163ffffffff90911681526020015b60405180910390f35b61038f61037a3660046133e9565b60036020526000908152604090205460ff1681565b6040519015158152602001610363565b6103b26103ad366004613418565b6107a2565b005b6103b26103c2366004613435565b610842565b6103da6103d5366004613476565b6108a3565b604051908152602001610363565b6103b26103f63660046134ef565b610908565b6103b26104093660046133e9565b6109f6565b6103b261041c366004613435565b610c65565b6103da60095481565b6103b26104383660046135a7565b610d56565b6103b261044b366004613613565b610eb1565b6103b261045e3660046133e9565b6110b2565b6103b26111b7565b6103b2610479366004613663565b611295565b60005461035290600160c81b900463ffffffff1681565b6103b26104a33660046136af565b6112b6565b6103b26104b6366004613476565b6112d3565b6104c3611820565b60405161036391906136c8565b61050a6104de3660046136af565b60046020526000908152604090205467ffffffffffffffff808216916801000000000000000090041682565b6040805167ffffffffffffffff938416815292909116602083015201610363565b6103b26105393660046133e9565b611882565b6103b261197b565b6105596105543660046136af565b6119cf565b60408051938452602084019290925290820152606001610363565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610363565b6103b26105c1366004613476565b611a07565b6103b26105d4366004613476565b611bf3565b6006546103529063ffffffff1681565b61059b6105f73660046136af565b611f84565b600a5461059b906001600160a01b031681565b60005461038f90600160a01b900460ff1681565b6000546001600160a01b031661059b565b6103b2610642366004613715565b611fae565b6103da600b5481565b6103b261065e3660046133e9565b6120c3565b6103b2612200565b6103b26122de565b6103b26106813660046136af565b612535565b6103da60075481565b6103b261268d565b6103b26106a53660046133e9565b612775565b61038f6106b83660046133e9565b60016020526000908152604090205460ff1681565b6103da6201518081565b6103da6106e53660046133e9565b60056020526000908152604090205481565b6103b2612898565b6103b261070d366004613418565b61296f565b6103da6402540be40081565b6008546103529063ffffffff1681565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6103b261078a3660046133e9565b612a03565b6103b261079d3660046134ef565b612ad3565b6000546001600160a01b031633146107ef5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064015b60405180910390fd5b426009556008805463ffffffff191663ffffffff83169081179091556040519081527f682bc0fb7e0d6bcb974cf556b95f68533cafc411d83d9f33ac192ccf45dda605906020015b60405180910390a150565b6000546001600160a01b0316331461088a5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b61089e6001600160a01b0384168383612b68565b505050565b600082826040516020016108e692919091825260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016602082015260240190565b6040516020818303038152906040528051906020012060001c90505b92915050565b6000546001600160a01b031633146109505760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517ffc4e51f60000000000000000000000000000000000000000000000000000000081526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063fc4e51f6906109bd9088908890889088908890600401613794565b600060405180830381600087803b1580156109d757600080fd5b505af11580156109eb573d6000803e3d6000fd5b505050505050505050565b33610a096000546001600160a01b031690565b6001600160a01b03161480610a2d57503360009081526003602052604090205460ff165b610a9f5760405162461bcd60e51b815260206004820152602360248201527f43616c6c6572206973206e6f7420746865206f776e6572206f7220677561726460448201527f69616e000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b03811660009081526001602052604090205460ff16610b075760405162461bcd60e51b815260206004820152601c60248201527f546869732061646472657373206973206e6f742061206d696e7465720000000060448201526064016107e6565b6001600160a01b0381166000908152600160205260408120805460ff191690555b600254811015610c2d57816001600160a01b031660028281548110610b4f57610b4f6137c7565b6000918252602090912001546001600160a01b031603610c1b5760028054610b79906001906137f3565b81548110610b8957610b896137c7565b600091825260209091200154600280546001600160a01b039092169183908110610bb557610bb56137c7565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506002805480610bf457610bf4613806565b600082815260209020810160001990810180546001600160a01b0319169055019055610c2d565b80610c258161381c565b915050610b28565b506040516001600160a01b038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000546001600160a01b03163314610cad5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517f1171bda90000000000000000000000000000000000000000000000000000000081526001600160a01b0384811660048301528381166024830152604482018390527f00000000000000000000000000000000000000000000000000000000000000001690631171bda9906064015b600060405180830381600087803b158015610d3957600080fd5b505af1158015610d4d573d6000803e3d6000fd5b50505050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610dce5760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6000839003610e1f5760405162461bcd60e51b815260206004820152601760248201527f4e6f206465706f7369746f72732073706563696669656400000000000000000060448201526064016107e6565b60005b83811015610eaa576000858583818110610e3e57610e3e6137c7565b9050602002016020810190610e5391906133e9565b90506000848484818110610e6957610e696137c7565b905060200201359050610e9582610e90846402540be40085610e8b9190613835565b612be8565b612ceb565b50508080610ea29061381c565b915050610e22565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f295760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6040516370a0823160e01b81526001600160a01b03858116600483015284917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa158015610f93573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb7919061384c565b10156110105760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b61102384610e906402540be40086613835565b604051631f1b6d2760e21b81526001600160a01b038581166004830152306024830152604482018590527f00000000000000000000000000000000000000000000000000000000000000001690637c6db49c906064015b600060405180830381600087803b15801561109457600080fd5b505af11580156110a8573d6000803e3d6000fd5b5050505050505050565b6000546001600160a01b031633146110fa5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b0381166111505760405162461bcd60e51b815260206004820181905260248201527f4e6577207661756c7420616464726573732063616e6e6f74206265207a65726f60448201526064016107e6565b604080516001600160a01b03831681524260208201527f5cc842cab066489e13292128663547c68705dbf476f0131e0107f155719c6124910160405180910390a142600b55600a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146111ff5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b60075461120f8162015180612dc6565b600654600080547fffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160a81b81029290921790556040519081527fa7f4ce7c3586e2000cdec6b25c5e7d0b20f9b4f435aa22d9c1feb32dbb506f779060200160405180910390a1506006805463ffffffff191690556000600755565b60006112a0846119cf565b505090506112b033828585612e6f565b50505050565b60006112c1826119cf565b505090506112cf3382612fb6565b5050565b3360009081526001602052604090205460ff166113325760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff161561138c5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b600061139883836108a3565b600081815260046020526040812080549293509167ffffffffffffffff16900361142a5760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff16156114b55760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60005481546114db91600160c81b900463ffffffff169067ffffffffffffffff16613865565b67ffffffffffffffff1642116115595760405162461bcd60e51b815260206004820152602b60248201527f4f7074696d6973746963206d696e74696e672064656c617920686173206e6f7460448201527f207061737365642079657400000000000000000000000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa1580156115c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115e591906138b9565b90508060a0015163ffffffff166000146116415760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60006402540be4008260800151836020015161165d9190613966565b67ffffffffffffffff166116719190613835565b6000805491925090600160a81b900463ffffffff166116915760006116ab565b6000546116ab90600160a81b900463ffffffff168361399d565b83516001600160a01b0316600090815260056020526040812054919250906116d49084906139b1565b84516001600160a01b03166000908152600560205260409020819055845190915061170390610e9084866137f3565b8115611794576117947f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166361d027b36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561176a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061178e91906139c4565b83612ceb565b84547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16680100000000000000004267ffffffffffffffff160217855583516040518281526001600160a01b0390911690879033907f2cffebf26d639426e79514d100febae8b2c63e700e5dc0fa6c88a129633506369060200160405180910390a45050505050505050565b6060600280548060200260200160405190810160405280929190818152602001828054801561187857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161185a575b5050505050905090565b6000546001600160a01b031633146118ca5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff166119325760405162461bcd60e51b815260206004820152601e60248201527f546869732061646472657373206973206e6f74206120677561726469616e000060448201526064016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19169055517fb8107d0c6b40be480ce3172ee66ba6d64b71f6b1685a851340036e6e2e3e3c529190a250565b6000546001600160a01b031633146119c35760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6119cd6000613105565b565b600080806119e26402540be400856139e1565b91506119ee82856137f3565b92506119ff6402540be4008461399d565b929491935050565b3360009081526003602052604090205460ff16611a665760405162461bcd60e51b815260206004820152601860248201527f43616c6c6572206973206e6f74206120677561726469616e000000000000000060448201526064016107e6565b6000611a7283836108a3565b600081815260046020526040812080549293509167ffffffffffffffff169003611b045760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff1615611b8f5760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60008281526004602052604080822080547fffffffffffffffffffffffffffffffff0000000000000000000000000000000016905551839133917f1256b41d4b18d922811c358ab80cb0375aae28f45373de35cfda580662193fcd9190a350505050565b3360009081526001602052604090205460ff16611c525760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff1615611cac5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b6000611cb883836108a3565b600081815260046020526040902080549192509067ffffffffffffffff1615611d495760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c726561647920726571756560448201527f7374656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa158015611db1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dd591906138b9565b9050806040015163ffffffff16600003611e3b5760405162461bcd60e51b815260206004820152602160248201527f546865206465706f73697420686173206e6f74206265656e2072657665616c656044820152601960fa1b60648201526084016107e6565b60a081015163ffffffff1615611e935760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60608101516001600160a01b03163014611eef5760405162461bcd60e51b815260206004820152601860248201527f556e6578706563746564207661756c742061646472657373000000000000000060448201526064016107e6565b815467ffffffffffffffff19164267ffffffffffffffff908116919091178355815160208301516001600160a01b0390911691859133917f36f39c606d55d7dd2a05b8c4e41e9a6ca8c501cea10009c1762f6826a146e05591611f59916402540be4009116613835565b60408051918252602082018b905263ffffffff8a169082015260600160405180910390a45050505050565b60028181548110611f9457600080fd5b6000918252602090912001546001600160a01b0316905081565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161461202f5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e206973206e6f74205442544300000000000000000000000000000060448201526064016107e6565b336001600160a01b038416146120875760405162461bcd60e51b815260206004820152601860248201527f4f6e6c7920544254432063616c6c657220616c6c6f776564000000000000000060448201526064016107e6565b6000612092856119cf565b509091505060008290036120af576120aa8682612fb6565b6120bb565b6120bb86828585612e6f565b505050505050565b6000546001600160a01b0316331461210b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526001602052604090205460ff16156121745760405162461bcd60e51b815260206004820181905260248201527f54686973206164647265737320697320616c72656164792061206d696e74657260448201526064016107e6565b6001600160a01b0381166000818152600160208190526040808320805460ff19168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180546001600160a01b03191684179055517f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f69190a250565b6000546001600160a01b031633146122485760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6009546122588162015180612dc6565b600854600080547fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160c81b81029290921790556040519081527ff52de3377e3ae270d1e38f99b9b8d587814643811516ea55ba4d597f9950d4ec9060200160405180910390a1506008805463ffffffff191690556000600955565b6000546001600160a01b031633146123265760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600b546123368162015180612dc6565b600a546040516001600160a01b0390911681527f81a9bb8030ed4116b405800280e065110a37afb57b69948e714c97fab23475ec9060200160405180910390a1600a546040517ff2fde38b0000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201527f00000000000000000000000000000000000000000000000000000000000000009091169063f2fde38b90602401600060405180830381600087803b1580156123f757600080fd5b505af115801561240b573d6000803e3d6000fd5b5050600a546040516370a0823160e01b81523060048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811694506356a6d9ef93509091169083906370a0823190602401602060405180830381865afa158015612483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124a7919061384c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561250557600080fd5b505af1158015612519573d6000803e3d6000fd5b5050600a80546001600160a01b031916905550506000600b5550565b600080612541836119cf565b6040516370a0823160e01b8152336004820152929450925082916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691506370a0823190602401602060405180830381865afa1580156125ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d1919061384c565b101561262a5760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b6126343383612ceb565b604051631f1b6d2760e21b8152336004820152306024820152604481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690637c6db49c90606401610d1f565b6000546001600160a01b031633146126d55760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff16156127395760405162461bcd60e51b815260206004820152602160248201527f4f7074696d6973746963206d696e74696e6720616c72656164792070617573656044820152601960fa1b60648201526084016107e6565b6000805460ff60a01b1916600160a01b1781556040517f23a83c8aeda8c831401c17b5bfb8b2ead79fcfe9c027fe34a4f8576e2c7c74cc9190a1565b6000546001600160a01b031633146127bd5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff161561284c5760405162461bcd60e51b815260206004820152602260248201527f54686973206164647265737320697320616c726561647920612067756172646960448201527f616e00000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19166001179055517f038596bb31e2e7d3d9f184d4c98b310103f6d7f5830e5eec32bffe6f1728f9699190a250565b6000546001600160a01b031633146128e05760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff166129395760405162461bcd60e51b815260206004820181905260248201527f4f7074696d6973746963206d696e74696e67206973206e6f742070617573656460448201526064016107e6565b6000805460ff60a01b191681556040517fcb27470ed9568d9eeb8939707bafc19404d908a26ce5f468a6aa781024fd6a839190a1565b6000546001600160a01b031633146129b75760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b426007556006805463ffffffff191663ffffffff83169081179091556040519081527f0dbfec7f12acffbb5cec595ac4370907eaf84caa7025dd71f4021433be79eba990602001610837565b6000546001600160a01b03163314612a4b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b038116612ac75760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016107e6565b612ad081613105565b50565b6000546001600160a01b03163314612b1b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde906109bd9030908890889088908890600401613794565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261089e908490613155565b6001600160a01b038216600090815260056020526040812054808203612c115782915050610902565b80831115612c78576001600160a01b0384166000818152600560209081526040808320839055519182527fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8910160405180910390a2612c7081846137f3565b915050610902565b612c8283826137f3565b6001600160a01b0385166000818152600560205260409020919091557fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8612cc985846137f3565b60405190815260200160405180910390a26000915050610902565b5092915050565b816001600160a01b03167f30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe82604051612d2691815260200190565b60405180910390a26040517f40c10f190000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906340c10f19906044015b600060405180830381600087803b158015612db257600080fd5b505af11580156120bb573d6000803e3d6000fd5b60008211612e165760405162461bcd60e51b815260206004820152601460248201527f4368616e6765206e6f7420696e6974696174656400000000000000000000000060448201526064016107e6565b80612e2183426137f3565b10156112cf5760405162461bcd60e51b815260206004820181905260248201527f476f7665726e616e63652064656c617920686173206e6f7420656c617073656460448201526064016107e6565b836001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b884604051612eaa91815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038581166004830152602482018590527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b158015612f1c57600080fd5b505af1158015612f30573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634a38757e7f00000000000000000000000000000000000000000000000000000000000000006402540be40086612f95919061399d565b85856040518563ffffffff1660e01b815260040161107a94939291906139f5565b816001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b882604051612ff191815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b15801561306357600080fd5b505af1158015613077573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356a6d9ef836402540be400846130bc919061399d565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401612d98565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006131aa826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661323a9092919063ffffffff16565b80519091501561089e57808060200190518101906131c89190613a28565b61089e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016107e6565b60606132498484600085613253565b90505b9392505050565b6060824710156132cb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b0385163b6133225760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107e6565b600080866001600160a01b0316858760405161333e9190613a6e565b60006040518083038185875af1925050503d806000811461337b576040519150601f19603f3d011682016040523d82523d6000602084013e613380565b606091505b509150915061339082828661339b565b979650505050505050565b606083156133aa57508161324c565b8251156133ba5782518084602001fd5b8160405162461bcd60e51b81526004016107e69190613a8a565b6001600160a01b0381168114612ad057600080fd5b6000602082840312156133fb57600080fd5b813561324c816133d4565b63ffffffff81168114612ad057600080fd5b60006020828403121561342a57600080fd5b813561324c81613406565b60008060006060848603121561344a57600080fd5b8335613455816133d4565b92506020840135613465816133d4565b929592945050506040919091013590565b6000806040838503121561348957600080fd5b82359150602083013561349b81613406565b809150509250929050565b60008083601f8401126134b857600080fd5b50813567ffffffffffffffff8111156134d057600080fd5b6020830191508360208285010111156134e857600080fd5b9250929050565b60008060008060006080868803121561350757600080fd5b8535613512816133d4565b94506020860135613522816133d4565b935060408601359250606086013567ffffffffffffffff81111561354557600080fd5b613551888289016134a6565b969995985093965092949392505050565b60008083601f84011261357457600080fd5b50813567ffffffffffffffff81111561358c57600080fd5b6020830191508360208260051b85010111156134e857600080fd5b600080600080604085870312156135bd57600080fd5b843567ffffffffffffffff808211156135d557600080fd5b6135e188838901613562565b909650945060208701359150808211156135fa57600080fd5b5061360787828801613562565b95989497509550505050565b6000806000806060858703121561362957600080fd5b8435613634816133d4565b935060208501359250604085013567ffffffffffffffff81111561365757600080fd5b613607878288016134a6565b60008060006040848603121561367857600080fd5b83359250602084013567ffffffffffffffff81111561369657600080fd5b6136a2868287016134a6565b9497909650939450505050565b6000602082840312156136c157600080fd5b5035919050565b6020808252825182820181905260009190848201906040850190845b818110156137095783516001600160a01b0316835292840192918401916001016136e4565b50909695505050505050565b60008060008060006080868803121561372d57600080fd5b8535613738816133d4565b945060208601359350604086013561374f816133d4565b9250606086013567ffffffffffffffff81111561354557600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60006001600160a01b0380881683528087166020840152508460408301526080606083015261339060808301848661376b565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115610902576109026137dd565b634e487b7160e01b600052603160045260246000fd5b60006001820161382e5761382e6137dd565b5060010190565b8082028115828204841417610902576109026137dd565b60006020828403121561385e57600080fd5b5051919050565b67ffffffffffffffff818116838216019080821115612ce457612ce46137dd565b8051613891816133d4565b919050565b805167ffffffffffffffff8116811461389157600080fd5b805161389181613406565b600060c082840312156138cb57600080fd5b60405160c0810181811067ffffffffffffffff821117156138fc57634e487b7160e01b600052604160045260246000fd5b60405261390883613886565b815261391660208401613896565b6020820152613927604084016138ae565b604082015261393860608401613886565b606082015261394960808401613896565b608082015261395a60a084016138ae565b60a08201529392505050565b67ffffffffffffffff828116828216039080821115612ce457612ce46137dd565b634e487b7160e01b600052601260045260246000fd5b6000826139ac576139ac613987565b500490565b80820180821115610902576109026137dd565b6000602082840312156139d657600080fd5b815161324c816133d4565b6000826139f0576139f0613987565b500690565b6001600160a01b0385168152836020820152606060408201526000613a1e60608301848661376b565b9695505050505050565b600060208284031215613a3a57600080fd5b8151801515811461324c57600080fd5b60005b83811015613a65578181015183820152602001613a4d565b50506000910152565b60008251613a80818460208701613a4a565b9190910192915050565b6020815260008251806020840152613aa9816040850160208701613a4a565b601f01601f1916919091016040019291505056fe4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572a2646970667358221220aabd28c13405d8cf2c5332bb69dcd504bd839194f1d43914aef46237f5af30d464736f6c63430008110033", + "devdoc": { + "details": "TBTC Vault is the owner of TBTC token contract and is the only contract minting the token.", + "kind": "dev", + "methods": { + "amountToSatoshis(uint256)": { + "returns": { + "convertibleAmount": "Amount of TBTC to be minted/unminted.", + "remainder": "Not convertible remainder if amount is not divisible by SATOSHI_MULTIPLIER.", + "satoshis": "Amount in satoshis - the Bank balance to be transferred for the given mint/unmint" + } + }, + "beginOptimisticMintingFeeUpdate(uint32)": { + "details": "See the documentation for optimisticMintingFeeDivisor." + }, + "cancelOptimisticMint(bytes32,uint32)": { + "details": "Guardians must validate the following conditions for every deposit for which the optimistic minting was requested: - The deposit happened on Bitcoin side and it has enough confirmations. - The optimistic minting has been requested early enough so that the wallet has enough time to sweep the deposit. - The wallet is an active one and it does perform sweeps or it will perform sweeps once the sweeps are activated." + }, + "initiateUpgrade(address)": { + "params": { + "_newVault": "The new vault address." + } + }, + "mint(uint256)": { + "details": "TBTC Vault must have an allowance for caller's balance in the Bank for at least `amount / SATOSHI_MULTIPLIER`.", + "params": { + "amount": "Amount of TBTC to mint." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "receiveApproval(address,uint256,address,bytes)": { + "details": "This function is doing the same as `unmint` or `unmintAndRedeem` (depending on `extraData` parameter) but it allows to execute unminting without a separate approval transaction. The function can be called only via `approveAndCall` of TBTC token.", + "params": { + "amount": "Amount of TBTC to unmint.", + "extraData": "Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function. If empty, `receiveApproval` is not requesting a redemption of Bank balance but is instead performing just TBTC unminting to a Bank balance.", + "from": "TBTC token holder executing unminting.", + "token": "TBTC token address." + } + }, + "receiveBalanceApproval(address,uint256,bytes)": { + "details": "Can only be called by the Bank via `approveBalanceAndCall`.", + "params": { + "owner": "The owner who approved their Bank balance.", + "satoshis": "Amount of satoshis used to mint TBTC." + } + }, + "receiveBalanceIncrease(address[],uint256[])": { + "details": "Fails if `depositors` array is empty. Expects the length of `depositors` and `depositedSatoshiAmounts` is the same." + }, + "recoverERC20(address,address,uint256)": { + "params": { + "amount": "Recovered amount.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC20 token contract." + } + }, + "recoverERC20FromToken(address,address,uint256)": { + "params": { + "amount": "Recovered amount.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC20 token contract." + } + }, + "recoverERC721(address,address,uint256,bytes)": { + "params": { + "data": "Additional data.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC721 token contract.", + "tokenId": "Identifier of the recovered token." + } + }, + "recoverERC721FromToken(address,address,uint256,bytes)": { + "params": { + "data": "Additional data.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC721 token contract.", + "tokenId": "Identifier of the recovered token." + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "requestOptimisticMint(bytes32,uint32)": { + "details": "The deposit done on the Bitcoin side must be revealed early enough to the Bridge on Ethereum to pass the Bridge's validation. The validation passes successfully only if the deposit reveal is done respectively earlier than the moment when the deposit refund locktime is reached, i.e. the deposit becomes refundable. It may happen that the wallet does not sweep a revealed deposit and one of the Minters requests an optimistic mint for that deposit just before the locktime is reached. Guardians must cancel optimistic minting for this deposit because the wallet will not be able to sweep it. The on-chain optimistic minting code does not perform any validation for gas efficiency: it would have to perform the same validation as `validateDepositRefundLocktime` and expect the entire `DepositRevealInfo` to be passed to assemble the expected script hash on-chain. Guardians must validate if the deposit happened on Bitcoin, that the script hash has the expected format, and that the wallet is an active one so they can also validate the time left for the refund." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + }, + "unmint(uint256)": { + "details": "Caller must have at least `amount` of TBTC approved to TBTC Vault.", + "params": { + "amount": "Amount of TBTC to unmint." + } + }, + "unmintAndRedeem(uint256,bytes)": { + "details": "Caller must have at least `amount` of TBTC approved to TBTC Vault.", + "params": { + "amount": "Amount of TBTC to unmint and request to redeem in Bridge.", + "redemptionData": "Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function." + } + } + }, + "title": "TBTC application vault", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "GOVERNANCE_DELAY()": { + "notice": "The time delay that needs to pass between initializing and finalizing the upgrade of governable parameters." + }, + "SATOSHI_MULTIPLIER()": { + "notice": "Multiplier to convert satoshi to TBTC token units." + }, + "addGuardian(address)": { + "notice": "Adds the address to the Guardian set." + }, + "addMinter(address)": { + "notice": "Adds the address to the Minter list." + }, + "amountToSatoshis(uint256)": { + "notice": "Returns the amount of TBTC to be minted/unminted, the remainder, and the Bank balance to be transferred for the given mint/unmint. Note that if the `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account when minting or unminting." + }, + "beginOptimisticMintingDelayUpdate(uint32)": { + "notice": "Begins the process of updating optimistic minting delay." + }, + "beginOptimisticMintingFeeUpdate(uint32)": { + "notice": "Begins the process of updating optimistic minting fee. The fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2% of each deposit, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`." + }, + "calculateDepositKey(bytes32,uint32)": { + "notice": "Calculates deposit key the same way as the Bridge contract. The deposit key is computed as `keccak256(fundingTxHash | fundingOutputIndex)`." + }, + "cancelOptimisticMint(bytes32,uint32)": { + "notice": "Allows a Guardian to cancel optimistic minting request. The following conditions must be met: - The optimistic minting request for the given deposit exists. - The optimistic minting request for the given deposit has not been finalized yet. Optimistic minting request is removed. It is possible to request optimistic minting again for the same deposit later." + }, + "finalizeOptimisticMint(bytes32,uint32)": { + "notice": "Allows a Minter to finalize previously requested optimistic minting. The following conditions must be met: - The optimistic minting has been requested for the given deposit. - The deposit has not been swept yet. - At least `optimisticMintingDelay` passed since the optimistic minting was requested for the given deposit. - The optimistic minting has not been finalized earlier for the given deposit. - The optimistic minting request for the given deposit has not been canceled by a Guardian. - The optimistic minting is not paused. This function mints TBTC and increases `optimisticMintingDebt` for the given depositor. The optimistic minting request is marked as finalized." + }, + "finalizeOptimisticMintingDelayUpdate()": { + "notice": "Finalizes the update process of the optimistic minting delay." + }, + "finalizeOptimisticMintingFeeUpdate()": { + "notice": "Finalizes the update process of the optimistic minting fee." + }, + "finalizeUpgrade()": { + "notice": "Allows the governance to finalize vault upgrade process. The upgrade process needs to be first initiated with a call to `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass. Once the upgrade is finalized, the new vault becomes the owner of the TBTC token and receives the whole Bank balance of this vault." + }, + "getMinters()": { + "notice": "Allows to fetch a list of all Minters." + }, + "initiateUpgrade(address)": { + "notice": "Initiates vault upgrade process. The upgrade process needs to be finalized with a call to `finalizeUpgrade` function after the `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can initiate the upgrade." + }, + "isGuardian(address)": { + "notice": "Indicates if the given address is a Guardian. Only Guardians can cancel requested optimistic minting." + }, + "isMinter(address)": { + "notice": "Indicates if the given address is a Minter. Only Minters can request optimistic minting." + }, + "isOptimisticMintingPaused()": { + "notice": "Indicates if the optimistic minting has been paused. Only the Governance can pause optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits." + }, + "mint(uint256)": { + "notice": "Mints the given `amount` of TBTC to the caller previously transferring `amount / SATOSHI_MULTIPLIER` of the Bank balance from caller to TBTC Vault. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's Bank balance." + }, + "minters(uint256)": { + "notice": "List of all Minters." + }, + "newOptimisticMintingDelay()": { + "notice": "New optimistic minting delay value. Set only when the parameter update process is pending. Once the update gets finalized, this" + }, + "newOptimisticMintingFeeDivisor()": { + "notice": "New optimistic minting fee divisor value. Set only when the parameter update process is pending. Once the update gets" + }, + "newVault()": { + "notice": "The address of a new TBTC vault. Set only when the upgrade process is pending. Once the upgrade gets finalized, the new TBTC vault will become an owner of TBTC token." + }, + "optimisticMintingDebt(address)": { + "notice": "Optimistic minting debt value per depositor's address. The debt represents the total value of all depositor's deposits revealed to the Bridge that has not been yet swept and led to the optimistic minting of TBTC. When `TBTCVault` sweeps a deposit, the debt is fully or partially paid off, no matter if that particular swept deposit was used for the optimistic minting or not. The values are in 1e18 Ethereum precision." + }, + "optimisticMintingDelay()": { + "notice": "The time that needs to pass between the moment the optimistic minting is requested and the moment optimistic minting is finalized with minting TBTC." + }, + "optimisticMintingDelayUpdateInitiatedTimestamp()": { + "notice": "The timestamp at which the update of the optimistic minting delay started. Zero if update is not in progress." + }, + "optimisticMintingFeeDivisor()": { + "notice": "Divisor used to compute the treasury fee taken from each optimistically minted deposit and transferred to the treasury upon finalization of the optimistic mint. This fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2%, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`. The optimistic minting fee does not replace the deposit treasury fee cut by the Bridge. The optimistic fee is a percentage AFTER the treasury fee is cut: `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`" + }, + "optimisticMintingFeeUpdateInitiatedTimestamp()": { + "notice": "The timestamp at which the update of the optimistic minting fee divisor started. Zero if update is not in progress." + }, + "optimisticMintingRequests(uint256)": { + "notice": "Collection of all revealed deposits for which the optimistic minting was requested. Indexed by a deposit key computed as `keccak256(fundingTxHash | fundingOutputIndex)`." + }, + "pauseOptimisticMinting()": { + "notice": "Pauses the optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits." + }, + "receiveApproval(address,uint256,address,bytes)": { + "notice": "Burns `amount` of TBTC from the caller's balance. If `extraData` is empty, transfers `amount` back to the caller's balance in the Bank. If `extraData` is not empty, requests redemption in the Bridge using the `extraData` as a `redemptionData` parameter to Bridge's `receiveBalanceApproval` function. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account. Note that it may left a token approval equal to the remainder." + }, + "receiveBalanceApproval(address,uint256,bytes)": { + "notice": "Transfers `satoshis` of the Bank balance from the caller to TBTC Vault and mints `satoshis * SATOSHI_MULTIPLIER` of TBTC to the caller." + }, + "receiveBalanceIncrease(address[],uint256[])": { + "notice": "Mints the same amount of TBTC as the deposited satoshis amount multiplied by SATOSHI_MULTIPLIER for each depositor in the array. Can only be called by the Bank after the Bridge swept deposits and Bank increased balance for the vault." + }, + "recoverERC20(address,address,uint256)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC20 token sent - mistakenly or not - to the vault address. This function should be used to withdraw TBTC v1 tokens transferred to TBTCVault as a result of VendingMachine > TBTCVault upgrade." + }, + "recoverERC20FromToken(address,address,uint256)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC20 token sent mistakenly to the TBTC token contract address." + }, + "recoverERC721(address,address,uint256,bytes)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the vault address." + }, + "recoverERC721FromToken(address,address,uint256,bytes)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the TBTC token contract address." + }, + "removeGuardian(address)": { + "notice": "Removes the address from the Guardian set." + }, + "removeMinter(address)": { + "notice": "Removes the address from the Minter list." + }, + "requestOptimisticMint(bytes32,uint32)": { + "notice": "Allows a Minter to request for an optimistic minting of TBTC. The following conditions must be met: - There is no optimistic minting request for the deposit, finalized or not. - The deposit with the given Bitcoin funding transaction hash and output index has been revealed to the Bridge. - The deposit has not been swept yet. - The deposit is targeted into the TBTCVault. - The optimistic minting is not paused. After calling this function, the Minter has to wait for `optimisticMintingDelay` before finalizing the mint with a call to finalizeOptimisticMint." + }, + "unmint(uint256)": { + "notice": "Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` back to the caller's balance in the Bank. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account." + }, + "unmintAndRedeem(uint256,bytes)": { + "notice": "Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` of Bank balance to the Bridge requesting redemption based on the provided `redemptionData`. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account." + }, + "unpauseOptimisticMinting()": { + "notice": "Unpauses the optimistic minting." + }, + "upgradeInitiatedTimestamp()": { + "notice": "The timestamp at which an upgrade to a new TBTC vault was initiated. Set only when the upgrade process is pending." + } + }, + "notice": "TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of Bitcoin. It facilitates Bitcoin holders to act on the Ethereum blockchain and access the decentralized finance (DeFi) ecosystem. TBTC Vault mints and unmints TBTC based on Bitcoin balances in the Bank.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 13249, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 33289, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "isOptimisticMintingPaused", + "offset": 20, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 33293, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingFeeDivisor", + "offset": 21, + "slot": "0", + "type": "t_uint32" + }, + { + "astId": 33297, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingDelay", + "offset": 25, + "slot": "0", + "type": "t_uint32" + }, + { + "astId": 33302, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "isMinter", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 33306, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "minters", + "offset": 0, + "slot": "2", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 33311, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "isGuardian", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 33317, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingRequests", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_uint256,t_struct(OptimisticMintingRequest)33273_storage)" + }, + { + "astId": 33322, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingDebt", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 33325, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "newOptimisticMintingFeeDivisor", + "offset": 0, + "slot": "6", + "type": "t_uint32" + }, + { + "astId": 33328, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingFeeUpdateInitiatedTimestamp", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 33331, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "newOptimisticMintingDelay", + "offset": 0, + "slot": "8", + "type": "t_uint32" + }, + { + "astId": 33334, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingDelayUpdateInitiatedTimestamp", + "offset": 0, + "slot": "9", + "type": "t_uint256" + }, + { + "astId": 34205, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "newVault", + "offset": 0, + "slot": "10", + "type": "t_address" + }, + { + "astId": 34208, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "upgradeInitiatedTimestamp", + "offset": 0, + "slot": "11", + "type": "t_uint256" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint256,t_struct(OptimisticMintingRequest)33273_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct TBTCOptimisticMinting.OptimisticMintingRequest)", + "numberOfBytes": "32", + "value": "t_struct(OptimisticMintingRequest)33273_storage" + }, + "t_struct(OptimisticMintingRequest)33273_storage": { + "encoding": "inplace", + "label": "struct TBTCOptimisticMinting.OptimisticMintingRequest", + "members": [ + { + "astId": 33270, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "requestedAt", + "offset": 0, + "slot": "0", + "type": "t_uint64" + }, + { + "astId": 33272, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "finalizedAt", + "offset": 8, + "slot": "0", + "type": "t_uint64" + } + ], + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } + } +} diff --git a/typescript/src/lib/ethereum/artifacts/goerli/WalletRegistry.json b/typescript/src/lib/ethereum/artifacts/goerli/WalletRegistry.json new file mode 100644 index 000000000..aa493fad5 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/goerli/WalletRegistry.json @@ -0,0 +1,1962 @@ +{ + "address": "0x0f0E2afF99A55B11026Fb270A05f04d37724dE86", + "abi": [ + { + "inputs": [ + { + "internalType": "contract SortitionPool", + "name": "_sortitionPool", + "type": "address" + }, + { + "internalType": "contract IStaking", + "name": "_staking", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "AuthorizationDecreaseApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "decreasingAt", + "type": "uint64" + } + ], + "name": "AuthorizationDecreaseRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "AuthorizationIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint96", + "name": "minimumAuthorization", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "authorizationDecreaseDelay", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "authorizationDecreaseChangePeriod", + "type": "uint64" + } + ], + "name": "AuthorizationParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashingAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "maliciousSubmitter", + "type": "address" + } + ], + "name": "DkgMaliciousResultSlashed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashingAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "maliciousSubmitter", + "type": "address" + } + ], + "name": "DkgMaliciousResultSlashingFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "seedTimeout", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultChallengePeriodLength", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultChallengeExtraGas", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultSubmissionTimeout", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultSubmitterPrecedencePeriodLength", + "type": "uint256" + } + ], + "name": "DkgParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "DkgResultApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "challenger", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "DkgResultChallenged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "seed", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "indexed": false, + "internalType": "struct EcdsaDkg.Result", + "name": "result", + "type": "tuple" + } + ], + "name": "DkgResultSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DkgSeedTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "seed", + "type": "uint256" + } + ], + "name": "DkgStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DkgStateLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DkgTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "dkgResultSubmissionGas", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dkgResultApprovalGasOffset", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "notifyOperatorInactivityGasOffset", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "notifySeedTimeoutGasOffset", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "notifyDkgTimeoutNegativeGasOffset", + "type": "uint256" + } + ], + "name": "GasParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldGovernance", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "GovernanceTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "notifier", + "type": "address" + } + ], + "name": "InactivityClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "InvoluntaryAuthorizationDecreaseFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorJoinedSortitionPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "randomBeacon", + "type": "address" + } + ], + "name": "RandomBeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newReimbursementPool", + "type": "address" + } + ], + "name": "ReimbursementPoolUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "maliciousDkgResultNotificationRewardMultiplier", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sortitionPoolRewardsBanDuration", + "type": "uint256" + } + ], + "name": "RewardParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "amount", + "type": "uint96" + } + ], + "name": "RewardsWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "maliciousDkgResultSlashingAmount", + "type": "uint256" + } + ], + "name": "SlashingParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "WalletClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "dkgResultHash", + "type": "bytes32" + } + ], + "name": "WalletCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "walletOwner", + "type": "address" + } + ], + "name": "WalletOwnerUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "relayEntry", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "__beaconCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "approveAuthorizationDecrease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "dkgResult", + "type": "tuple" + } + ], + "name": "approveDkgResult", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "authorizationDecreaseRequested", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "authorizationIncreased", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "authorizationParameters", + "outputs": [ + { + "internalType": "uint96", + "name": "minimumAuthorization", + "type": "uint96" + }, + { + "internalType": "uint64", + "name": "authorizationDecreaseDelay", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "authorizationDecreaseChangePeriod", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "availableRewards", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "dkgResult", + "type": "tuple" + } + ], + "name": "challengeDkgResult", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "closeWallet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dkgParameters", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "seedTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resultChallengePeriodLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resultChallengeExtraGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resultSubmissionTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "submitterPrecedencePeriodLength", + "type": "uint256" + } + ], + "internalType": "struct EcdsaDkg.Parameters", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "eligibleStake", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasParameters", + "outputs": [ + { + "internalType": "uint256", + "name": "dkgResultSubmissionGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dkgResultApprovalGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyOperatorInactivityGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifySeedTimeoutGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyDkgTimeoutNegativeGasOffset", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "getWallet", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "membersIdsHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "internalType": "struct Wallets.Wallet", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWalletCreationState", + "outputs": [ + { + "internalType": "enum EcdsaDkg.State", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "getWalletPublicKey", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasDkgTimedOut", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasSeedTimedOut", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "inactivityClaimNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract EcdsaDkgValidator", + "name": "_ecdsaDkgValidator", + "type": "address" + }, + { + "internalType": "contract IRandomBeacon", + "name": "_randomBeacon", + "type": "address" + }, + { + "internalType": "contract ReimbursementPool", + "name": "_reimbursementPool", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "involuntaryAuthorizationDecrease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "result", + "type": "tuple" + } + ], + "name": "isDkgResultValid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isOperatorInPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isOperatorUpToDate", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "walletMemberIndex", + "type": "uint256" + } + ], + "name": "isWalletMember", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "isWalletRegistered", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "joinSortitionPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "minimumAuthorization", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "notifyDkgTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "inactiveMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "heartbeatFailed", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + } + ], + "internalType": "struct EcdsaInactivity.Claim", + "name": "claim", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint32[]", + "name": "groupMembers", + "type": "uint32[]" + } + ], + "name": "notifyOperatorInactivity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "notifySeedTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "operatorToStakingProvider", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "pendingAuthorizationDecrease", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "randomBeacon", + "outputs": [ + { + "internalType": "contract IRandomBeacon", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reimbursementPool", + "outputs": [ + { + "internalType": "contract ReimbursementPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "remainingAuthorizationDecreaseDelay", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "requestNewWallet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardParameters", + "outputs": [ + { + "internalType": "uint256", + "name": "maliciousDkgResultNotificationRewardMultiplier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sortitionPoolRewardsBanDuration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + }, + { + "internalType": "uint256", + "name": "rewardMultiplier", + "type": "uint256" + }, + { + "internalType": "address", + "name": "notifier", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + } + ], + "name": "seize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "selectGroup", + "outputs": [ + { + "internalType": "uint32[]", + "name": "", + "type": "uint32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "slashingParameters", + "outputs": [ + { + "internalType": "uint96", + "name": "maliciousDkgResultSlashingAmount", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sortitionPool", + "outputs": [ + { + "internalType": "contract SortitionPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "staking", + "outputs": [ + { + "internalType": "contract IStaking", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "stakingProviderToOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "dkgResult", + "type": "tuple" + } + ], + "name": "submitDkgResult", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "_minimumAuthorization", + "type": "uint96" + }, + { + "internalType": "uint64", + "name": "_authorizationDecreaseDelay", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_authorizationDecreaseChangePeriod", + "type": "uint64" + } + ], + "name": "updateAuthorizationParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_seedTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_resultChallengePeriodLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_resultChallengeExtraGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_resultSubmissionTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_submitterPrecedencePeriodLength", + "type": "uint256" + } + ], + "name": "updateDkgParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dkgResultSubmissionGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dkgResultApprovalGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyOperatorInactivityGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifySeedTimeoutGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyDkgTimeoutNegativeGasOffset", + "type": "uint256" + } + ], + "name": "updateGasParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "updateOperatorStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ReimbursementPool", + "name": "_reimbursementPool", + "type": "address" + } + ], + "name": "updateReimbursementPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maliciousDkgResultNotificationRewardMultiplier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sortitionPoolRewardsBanDuration", + "type": "uint256" + } + ], + "name": "updateRewardParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "maliciousDkgResultSlashingAmount", + "type": "uint96" + } + ], + "name": "updateSlashingParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IWalletOwner", + "name": "_walletOwner", + "type": "address" + } + ], + "name": "updateWalletOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRandomBeacon", + "name": "_randomBeacon", + "type": "address" + } + ], + "name": "upgradeRandomBeacon", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "walletOwner", + "outputs": [ + { + "internalType": "contract IWalletOwner", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "withdrawIneligibleRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "withdrawRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xf40a2de63c196595c23cb8c78a0dd237ea6b3ed21803518b543245cda65cd1a8", + "receipt": { + "to": null, + "from": "0x68ad60CC5e8f3B7cC53beaB321cf0e6036962dBc", + "contractAddress": "0x0f0E2afF99A55B11026Fb270A05f04d37724dE86", + "transactionIndex": 10, + "gasUsed": "1053777", + "logsBloom": "0x00000000000000000000000000400000400000004000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000800000000800000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000040000000000000000000000080000000000000800000000000000000000000000000000400000000000000000000000000000000000000000020000000000000000000040000002000000400000000000000000800000000000000000000000800000000000000000000000000000000000000000000", + "blockHash": "0x848e125a89628cf2e92b414febe60f4fa0d32b38d20696dc6c7c864cae1e9a80", + "transactionHash": "0xf40a2de63c196595c23cb8c78a0dd237ea6b3ed21803518b543245cda65cd1a8", + "logs": [ + { + "transactionIndex": 10, + "blockNumber": 8364851, + "transactionHash": "0xf40a2de63c196595c23cb8c78a0dd237ea6b3ed21803518b543245cda65cd1a8", + "address": "0x0f0E2afF99A55B11026Fb270A05f04d37724dE86", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x0000000000000000000000005b1ebf8008097ac3ef6785220bfb9eca2b1a73dd" + ], + "data": "0x", + "logIndex": 13, + "blockHash": "0x848e125a89628cf2e92b414febe60f4fa0d32b38d20696dc6c7c864cae1e9a80" + }, + { + "transactionIndex": 10, + "blockNumber": 8364851, + "transactionHash": "0xf40a2de63c196595c23cb8c78a0dd237ea6b3ed21803518b543245cda65cd1a8", + "address": "0x0f0E2afF99A55B11026Fb270A05f04d37724dE86", + "topics": [ + "0x5f56bee8cffbe9a78652a74a60705edede02af10b0bbb888ca44b79a0d42ce80" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000068ad60cc5e8f3b7cc53beab321cf0e6036962dbc", + "logIndex": 14, + "blockHash": "0x848e125a89628cf2e92b414febe60f4fa0d32b38d20696dc6c7c864cae1e9a80" + }, + { + "transactionIndex": 10, + "blockNumber": 8364851, + "transactionHash": "0xf40a2de63c196595c23cb8c78a0dd237ea6b3ed21803518b543245cda65cd1a8", + "address": "0x0f0E2afF99A55B11026Fb270A05f04d37724dE86", + "topics": [ + "0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 15, + "blockHash": "0x848e125a89628cf2e92b414febe60f4fa0d32b38d20696dc6c7c864cae1e9a80" + }, + { + "transactionIndex": 10, + "blockNumber": 8364851, + "transactionHash": "0xf40a2de63c196595c23cb8c78a0dd237ea6b3ed21803518b543245cda65cd1a8", + "address": "0x0f0E2afF99A55B11026Fb270A05f04d37724dE86", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cbcf87d133123f7b48e4343fa1861d4c0d6eef24", + "logIndex": 16, + "blockHash": "0x848e125a89628cf2e92b414febe60f4fa0d32b38d20696dc6c7c864cae1e9a80" + } + ], + "blockNumber": 8364851, + "cumulativeGasUsed": "3144686", + "status": 1, + "byzantium": true + }, + "args": [ + "0x3985b5320148D04fe0D1eE9D6380caD49baF2CD4", + "0x1da5d88C26EA4f87b5e09C3452eE2384Ee20DC75" + ], + "numDeployments": 1, + "libraries": { + "EcdsaInactivity": "0x7102345e1Eb62aE60044f0c4B8Ad97984D27fcf6" + }, + "implementation": "0x5b1ebF8008097Ac3EF6785220bFb9ecA2B1a73Dd", + "devdoc": "Contract deployed as upgradable proxy" +} diff --git a/typescript/src/lib/ethereum/artifacts/mainnet/Bridge.json b/typescript/src/lib/ethereum/artifacts/mainnet/Bridge.json new file mode 100644 index 000000000..5ca597163 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/mainnet/Bridge.json @@ -0,0 +1,2624 @@ +{ + "address": "0x5e4861a80B55f035D899f66772117F00FA0E8e7B", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "depositDustThreshold", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "depositTreasuryFeeDivisor", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "depositTxMaxFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "depositRevealAheadPeriod", + "type": "uint32" + } + ], + "name": "DepositParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "amount", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes8", + "name": "blindingFactor", + "type": "bytes8" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes20", + "name": "refundPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes4", + "name": "refundLocktime", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "DepositRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sweepTxHash", + "type": "bytes32" + } + ], + "name": "DepositsSwept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sighash", + "type": "bytes32" + } + ], + "name": "FraudChallengeDefeatTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sighash", + "type": "bytes32" + } + ], + "name": "FraudChallengeDefeated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sighash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "FraudChallengeSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint96", + "name": "fraudChallengeDepositAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fraudChallengeDefeatTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fraudSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fraudNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "FraudParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldGovernance", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "GovernanceTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "movingFundsTxHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTxOutputIndex", + "type": "uint32" + } + ], + "name": "MovedFundsSweepTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "sweepTxHash", + "type": "bytes32" + } + ], + "name": "MovedFundsSwept", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "MovingFundsBelowDustReported", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes20[]", + "name": "targetWallets", + "type": "bytes20[]" + }, + { + "indexed": false, + "internalType": "address", + "name": "submitter", + "type": "address" + } + ], + "name": "MovingFundsCommitmentSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "movingFundsTxHash", + "type": "bytes32" + } + ], + "name": "MovingFundsCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "movingFundsTxMaxTotalFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "movingFundsDustThreshold", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTimeoutResetDelay", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "movingFundsTimeoutSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movingFundsTimeoutNotifierRewardMultiplier", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "movingFundsCommitmentGasOffset", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "movedFundsSweepTxMaxTotalFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movedFundsSweepTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "movedFundsSweepTimeoutSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "movedFundsSweepTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "MovingFundsParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "MovingFundsTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "MovingFundsTimeoutReset", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "NewWalletRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "NewWalletRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionDustThreshold", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionTreasuryFeeDivisor", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionTxMaxFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "redemptionTxMaxTotalFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "redemptionTimeout", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "redemptionTimeoutSlashingAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "redemptionTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "RedemptionParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + }, + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "requestedAmount", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "txMaxFee", + "type": "uint64" + } + ], + "name": "RedemptionRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + } + ], + "name": "RedemptionTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "redemptionTxHash", + "type": "bytes32" + } + ], + "name": "RedemptionsCompleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "spvMaintainer", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "SpvMaintainerStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "TreasuryUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "VaultStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletClosing", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletMovingFunds", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "walletCreationPeriod", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletCreationMinBtcBalance", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletCreationMaxBtcBalance", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletClosureMinBtcBalance", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "walletMaxAge", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "walletMaxBtcTransfer", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "walletClosingPeriod", + "type": "uint32" + } + ], + "name": "WalletParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "WalletTerminated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "name": "__ecdsaWalletCreatedCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "name": "__ecdsaWalletHeartbeatFailedCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "activeWalletPubKeyHash", + "outputs": [ + { + "internalType": "bytes20", + "name": "", + "type": "bytes20" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "contractReferences", + "outputs": [ + { + "internalType": "contract Bank", + "name": "bank", + "type": "address" + }, + { + "internalType": "contract IRelay", + "name": "relay", + "type": "address" + }, + { + "internalType": "contract IWalletRegistry", + "name": "ecdsaWalletRegistry", + "type": "address" + }, + { + "internalType": "contract ReimbursementPool", + "name": "reimbursementPool", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "preimage", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "witness", + "type": "bool" + } + ], + "name": "defeatFraudChallenge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "heartbeatMessage", + "type": "bytes" + } + ], + "name": "defeatFraudChallengeWithHeartbeat", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "depositParameters", + "outputs": [ + { + "internalType": "uint64", + "name": "depositDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "depositRevealAheadPeriod", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + } + ], + "name": "deposits", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "revealedAt", + "type": "uint32" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "sweptAt", + "type": "uint32" + } + ], + "internalType": "struct Deposit.DepositRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "challengeKey", + "type": "uint256" + } + ], + "name": "fraudChallenges", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "challenger", + "type": "address" + }, + { + "internalType": "uint256", + "name": "depositAmount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "reportedAt", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "resolved", + "type": "bool" + } + ], + "internalType": "struct Fraud.FraudChallenge", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fraudParameters", + "outputs": [ + { + "internalType": "uint96", + "name": "fraudChallengeDepositAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudChallengeDefeatTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "fraudSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudNotifierRewardMultiplier", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_bank", + "type": "address" + }, + { + "internalType": "address", + "name": "_relay", + "type": "address" + }, + { + "internalType": "address", + "name": "_treasury", + "type": "address" + }, + { + "internalType": "address", + "name": "_ecdsaWalletRegistry", + "type": "address" + }, + { + "internalType": "address payable", + "name": "_reimbursementPool", + "type": "address" + }, + { + "internalType": "uint96", + "name": "_txProofDifficultyFactor", + "type": "uint96" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "isVaultTrusted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "liveWalletsCount", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requestKey", + "type": "uint256" + } + ], + "name": "movedFundsSweepRequests", + "outputs": [ + { + "components": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "uint64", + "name": "value", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "createdAt", + "type": "uint32" + }, + { + "internalType": "enum MovingFunds.MovedFundsSweepRequestState", + "name": "state", + "type": "uint8" + } + ], + "internalType": "struct MovingFunds.MovedFundsSweepRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "movingFundsParameters", + "outputs": [ + { + "internalType": "uint64", + "name": "movingFundsTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "movingFundsDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutResetDelay", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movingFundsTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutNotifierRewardMultiplier", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "movingFundsCommitmentGasOffset", + "type": "uint16" + }, + { + "internalType": "uint64", + "name": "movedFundsSweepTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movedFundsSweepTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "bytes", + "name": "preimageSha256", + "type": "bytes" + } + ], + "name": "notifyFraudChallengeDefeatTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "movingFundsTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "movingFundsTxOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + } + ], + "name": "notifyMovedFundsSweepTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + } + ], + "name": "notifyMovingFundsBelowDust", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + } + ], + "name": "notifyMovingFundsTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + } + ], + "name": "notifyRedemptionTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "walletMainUtxo", + "type": "tuple" + } + ], + "name": "notifyWalletCloseable", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "notifyWalletClosingPeriodElapsed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redemptionKey", + "type": "uint256" + } + ], + "name": "pendingRedemptions", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestedAmount", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "txMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "requestedAt", + "type": "uint32" + } + ], + "internalType": "struct Redemption.RedemptionRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "balanceOwner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "redemptionData", + "type": "bytes" + } + ], + "name": "receiveBalanceApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "redemptionParameters", + "outputs": [ + { + "internalType": "uint64", + "name": "redemptionDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "redemptionTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "redemptionTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "redemptionTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "activeWalletMainUtxo", + "type": "tuple" + } + ], + "name": "requestNewWallet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "redeemerOutputScript", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + } + ], + "name": "requestRedemption", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "resetMovingFundsTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "fundingTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + }, + { + "internalType": "bytes8", + "name": "blindingFactor", + "type": "bytes8" + }, + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "bytes20", + "name": "refundPubKeyHash", + "type": "bytes20" + }, + { + "internalType": "bytes4", + "name": "refundLocktime", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "internalType": "struct Deposit.DepositRevealInfo", + "name": "reveal", + "type": "tuple" + } + ], + "name": "revealDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spvMaintainer", + "type": "address" + }, + { + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "setSpvMaintainerStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "bool", + "name": "isTrusted", + "type": "bool" + } + ], + "name": "setVaultStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "utxoKey", + "type": "uint256" + } + ], + "name": "spentMainUTXOs", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "sweepTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "sweepProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + } + ], + "name": "submitDepositSweepProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "walletPublicKey", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "preimageSha256", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + } + ], + "internalType": "struct BitcoinTx.RSVSignature", + "name": "signature", + "type": "tuple" + } + ], + "name": "submitFraudChallenge", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "sweepTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "sweepProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + } + ], + "name": "submitMovedFundsSweepProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "walletMainUtxo", + "type": "tuple" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "uint256", + "name": "walletMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes20[]", + "name": "targetWallets", + "type": "bytes20[]" + } + ], + "name": "submitMovingFundsCommitment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "movingFundsTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "movingFundsProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "submitMovingFundsProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes4", + "name": "version", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "inputVector", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "outputVector", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "locktime", + "type": "bytes4" + } + ], + "internalType": "struct BitcoinTx.Info", + "name": "redemptionTx", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes", + "name": "merkleProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "txIndexInBlock", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "bitcoinHeaders", + "type": "bytes" + } + ], + "internalType": "struct BitcoinTx.Proof", + "name": "redemptionProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "txOutputIndex", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "txOutputValue", + "type": "uint64" + } + ], + "internalType": "struct BitcoinTx.UTXO", + "name": "mainUtxo", + "type": "tuple" + }, + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "submitRedemptionProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "redemptionKey", + "type": "uint256" + } + ], + "name": "timedOutRedemptions", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "internalType": "uint64", + "name": "requestedAmount", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "treasuryFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "txMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "requestedAt", + "type": "uint32" + } + ], + "internalType": "struct Redemption.RedemptionRequest", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "txProofDifficultyFactor", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "depositDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "depositTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "depositRevealAheadPeriod", + "type": "uint32" + } + ], + "name": "updateDepositParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "fraudChallengeDepositAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudChallengeDefeatTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "fraudSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "fraudNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "updateFraudParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "movingFundsTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "movingFundsDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutResetDelay", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movingFundsTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movingFundsTimeoutNotifierRewardMultiplier", + "type": "uint32" + }, + { + "internalType": "uint16", + "name": "movingFundsCommitmentGasOffset", + "type": "uint16" + }, + { + "internalType": "uint64", + "name": "movedFundsSweepTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "movedFundsSweepTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "movedFundsSweepTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "updateMovingFundsParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "redemptionDustThreshold", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTreasuryFeeDivisor", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxFee", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "redemptionTxMaxTotalFee", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "redemptionTimeout", + "type": "uint32" + }, + { + "internalType": "uint96", + "name": "redemptionTimeoutSlashingAmount", + "type": "uint96" + }, + { + "internalType": "uint32", + "name": "redemptionTimeoutNotifierRewardMultiplier", + "type": "uint32" + } + ], + "name": "updateRedemptionParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "treasury", + "type": "address" + } + ], + "name": "updateTreasury", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "walletCreationPeriod", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletCreationMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletCreationMaxBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletClosureMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletMaxAge", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletMaxBtcTransfer", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletClosingPeriod", + "type": "uint32" + } + ], + "name": "updateWalletParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "walletParameters", + "outputs": [ + { + "internalType": "uint32", + "name": "walletCreationPeriod", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletCreationMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletCreationMaxBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "walletClosureMinBtcBalance", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletMaxAge", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "walletMaxBtcTransfer", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "walletClosingPeriod", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes20", + "name": "walletPubKeyHash", + "type": "bytes20" + } + ], + "name": "wallets", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "ecdsaWalletID", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "mainUtxoHash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "pendingRedemptionsValue", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "createdAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "movingFundsRequestedAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "closingStartedAt", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "pendingMovedFundsSweepRequestsCount", + "type": "uint32" + }, + { + "internalType": "enum Wallets.WalletState", + "name": "state", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "movingFundsTargetWalletsCommitmentHash", + "type": "bytes32" + } + ], + "internalType": "struct Wallets.Wallet", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0xa2980d644ed3713ca5ebd545dbd4daee76745ebaff4aca2e4222b859a8400ced", + "receipt": { + "to": "0x16A76d3cd3C1e3CE843C6680d6B37E9116b5C706", + "from": "0x123694886DBf5Ac94DDA07135349534536D14cAf", + "contractAddress": null, + "transactionIndex": 144, + "gasUsed": "38748", + "logsBloom": "0x00000000000000000000000000000000400000000000000000000000000000000000000000000000000000000200000000000000000000020000000000000000100000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000001000000000020000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3042ecc12f13d8a45f547456da41ee726fc960ab5e430bfe1184823d94439094", + "transactionHash": "0xa2980d644ed3713ca5ebd545dbd4daee76745ebaff4aca2e4222b859a8400ced", + "logs": [ + { + "transactionIndex": 144, + "blockNumber": 16399102, + "transactionHash": "0xa2980d644ed3713ca5ebd545dbd4daee76745ebaff4aca2e4222b859a8400ced", + "address": "0x5e4861a80B55f035D899f66772117F00FA0E8e7B", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x0000000000000000000000008d014903bf7867260584d714e11809fea5293234" + ], + "data": "0x", + "logIndex": 167, + "blockHash": "0x3042ecc12f13d8a45f547456da41ee726fc960ab5e430bfe1184823d94439094" + } + ], + "blockNumber": 16399102, + "cumulativeGasUsed": "8682572", + "status": 1, + "byzantium": true + }, + "numDeployments": 2, + "libraries": { + "Deposit": "0x1Ac1841a47054E070236F1664991e52c30C04dC5", + "DepositSweep": "0xE6F919e62c370Ef04EDF4cA5E2a901Ea1aa87085", + "Redemption": "0xfa4FC53BEB5B7721d173342FfC24058e9D47a1Ff", + "Wallets": "0xC67913137429985416DbCe28D9fa9ec960BA47BF", + "Fraud": "0xd040Def4bC6372Cd27b433288008aDCe7c9de71C", + "MovingFunds": "0xB857117ae7639255d7305DdbC7346b4a644CA432" + }, + "implementation": "0x8D014903BF7867260584d714e11809fea5293234", + "devdoc": "Contract deployed as upgradable proxy" +} diff --git a/typescript/src/lib/ethereum/artifacts/mainnet/TBTCToken.json b/typescript/src/lib/ethereum/artifacts/mainnet/TBTCToken.json new file mode 100644 index 000000000..dbb8de123 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/mainnet/TBTCToken.json @@ -0,0 +1,762 @@ +{ + "address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "approveAndCall", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cachedChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "cachedDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x52538ac60ce0a5672aa77991e9f030ca6eed76db0bf027821ed5170b29971ba2", + "receipt": { + "to": null, + "from": "0x123694886DBf5Ac94DDA07135349534536D14cAf", + "contractAddress": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "transactionIndex": 44, + "gasUsed": "2909503", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000040000001000000000000000000020000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x3586b492c74f630a840cbf65b80646da78860364cbdc7976521aee86449a6759", + "transactionHash": "0x52538ac60ce0a5672aa77991e9f030ca6eed76db0bf027821ed5170b29971ba2", + "logs": [ + { + "transactionIndex": 44, + "blockNumber": 13042356, + "transactionHash": "0x52538ac60ce0a5672aa77991e9f030ca6eed76db0bf027821ed5170b29971ba2", + "address": "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000123694886dbf5ac94dda07135349534536d14caf" + ], + "data": "0x", + "logIndex": 54, + "blockHash": "0x3586b492c74f630a840cbf65b80646da78860364cbdc7976521aee86449a6759" + } + ], + "blockNumber": 13042356, + "cumulativeGasUsed": "5443048", + "status": 1, + "byzantium": true + }, + "args": [], + "solcInputHash": "7cc3eda3cb3ff2522d18b5e7b31ea228", + "metadata": "{\"compiler\":{\"version\":\"0.8.4+commit.c7e474f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"approveAndCall\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burnFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"cachedDomainSeparator\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"approve(address,uint256)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\",\"returns\":{\"_0\":\"True if the operation succeeded.\"}},\"approveAndCall(address,uint256,bytes)\":{\"details\":\"If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\",\"returns\":{\"_0\":\"True if both approval and `receiveApproval` calls succeeded.\"}},\"burn(uint256)\":{\"details\":\"Requirements: - the caller must have a balance of at least `amount`.\"},\"burnFrom(address,uint256)\":{\"details\":\"Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`.\"},\"mint(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address.\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"details\":\"The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"transfer(address,uint256)\":{\"details\":\"Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferFrom(address,address,uint256)\":{\"details\":\"Requirements: - `sender` and `recipient` cannot be the zero address, - `sender` must have a balance of at least `amount`, - the caller must have allowance for `sender`'s tokens of at least `amount`.\",\"returns\":{\"_0\":\"True if the operation succeeded, reverts otherwise.\"}},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"DOMAIN_SEPARATOR()\":{\"notice\":\"Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function.\"},\"PERMIT_TYPEHASH()\":{\"notice\":\"Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function.\"},\"allowance(address,address)\":{\"notice\":\"The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default.\"},\"approve(address,uint256)\":{\"notice\":\"Sets `amount` as the allowance of `spender` over the caller's tokens.\"},\"approveAndCall(address,uint256,bytes)\":{\"notice\":\"Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted.\"},\"balanceOf(address)\":{\"notice\":\"The amount of tokens owned by the given account.\"},\"burn(uint256)\":{\"notice\":\"Destroys `amount` tokens from the caller.\"},\"burnFrom(address,uint256)\":{\"notice\":\"Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"},\"decimals()\":{\"notice\":\"The decimals places of the token.\"},\"mint(address,uint256)\":{\"notice\":\"Creates `amount` tokens and assigns them to `account`, increasing the total supply.\"},\"name()\":{\"notice\":\"The name of the token.\"},\"nonces(address)\":{\"notice\":\"Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function.\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"notice\":\"EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction.\"},\"symbol()\":{\"notice\":\"The symbol of the token.\"},\"totalSupply()\":{\"notice\":\"The amount of tokens in existence.\"},\"transfer(address,uint256)\":{\"notice\":\"Moves `amount` tokens from the caller's account to `recipient`.\"},\"transferFrom(address,address,uint256)\":{\"notice\":\"Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/token/TBTC.sol\":\"TBTC\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _setOwner(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _setOwner(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _setOwner(newOwner);\\n }\\n\\n function _setOwner(address newOwner) private {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0x6bb804a310218875e89d12c053e94a13a4607cdf7cc2052f3e52bd32a0dc50a1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x027b891937d20ccf213fdb9c31531574256de774bda99d3a70ecef6e1913ed2a\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x83fe24f5c04a56091e50f4a345ff504c8bff658a76d4c43b16878c8f940c53b2\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0x02348b2e4b9f3200c7e3907c5c2661643a6d8520e9f79939fbb9b4005a54894d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n}\\n\",\"keccak256\":\"0xf101e8720213560fab41104d53b3cc7ba0456ef3a98455aa7f022391783144a0\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize, which returns 0 for contracts in\\n // construction, since the code is only stored at the end of the\\n // constructor execution.\\n\\n uint256 size;\\n assembly {\\n size := extcodesize(account)\\n }\\n return size > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return _verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return _verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return _verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n function _verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) private pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3b4820cac4f127869f6eb496c1d74fa6ac86ed24071e0f94742e6aef20e7252c\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/*\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0x95098bd1d9c8dec4d80d3dedb88a0d949fa0d740ee99f2aa466bc308216ca6d5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xa28007762d9da9db878dd421960c8cb9a10471f47ab5c1b3309bfe48e9e79ff4\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonces;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `sender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `sender` and `recipient` cannot be the zero address,\\n /// - `sender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `sender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[sender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(sender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonces[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address sender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(sender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(sender, recipient, amount);\\n\\n uint256 senderBalance = balanceOf[sender];\\n require(senderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[sender] = senderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(sender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x03d73b1cb4915aee716e6245c0bb79335bbe0510cf16a08b2228151eb35d3183\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonces(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xe6ecbd8d29688969b11bedb4ce1d076be09a69c97d31ae89c913734ece5d61fc\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/token/TBTC.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity 0.8.4;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\\n constructor() ERC20WithPermit(\\\"tBTC v2\\\", \\\"tBTC\\\") {}\\n}\\n\",\"keccak256\":\"0xc139767d54b9d525f99cee906d2f6ee5b0c049fa97606e39ed88ccc4858c1ad3\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60c06040523480156200001157600080fd5b506040518060400160405280600781526020017f74425443207632000000000000000000000000000000000000000000000000008152506040518060400160405280600481526020017f74425443000000000000000000000000000000000000000000000000000000008152506200009e62000092620000f760201b60201c565b620000ff60201b60201c565b8160059080519060200190620000b69291906200026f565b508060069080519060200190620000cf9291906200026f565b504660808181525050620000e8620001c360201b60201c565b60a08181525050505062000520565b600033905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6005604051620001f79190620003dd565b60405180910390206040518060400160405280600181526020017f310000000000000000000000000000000000000000000000000000000000000081525080519060200120463060405160200162000254959493929190620003f6565b60405160208183030381529060405280519060200120905090565b8280546200027d90620004bb565b90600052602060002090601f016020900481019282620002a15760008555620002ed565b82601f10620002bc57805160ff1916838001178555620002ed565b82800160010185558215620002ed579182015b82811115620002ec578251825591602001919060010190620002cf565b5b509050620002fc919062000300565b5090565b5b808211156200031b57600081600090555060010162000301565b5090565b6200032a8162000473565b82525050565b6200033b8162000487565b82525050565b600081546200035081620004bb565b6200035c818662000468565b945060018216600081146200037a57600181146200038c57620003c3565b60ff19831686528186019350620003c3565b620003978562000453565b60005b83811015620003bb578154818901526001820191506020810190506200039a565b838801955050505b50505092915050565b620003d781620004b1565b82525050565b6000620003eb828462000341565b915081905092915050565b600060a0820190506200040d600083018862000330565b6200041c602083018762000330565b6200042b604083018662000330565b6200043a6060830185620003cc565b6200044960808301846200031f565b9695505050505050565b60008190508160005260206000209050919050565b600081905092915050565b6000620004808262000491565b9050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006002820490506001821680620004d457607f821691505b60208210811415620004eb57620004ea620004f1565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60805160a05161324d620005546000396000818161074f0152610c1e0152600081816107270152610a03015261324d6000f3fe608060405234801561001057600080fd5b50600436106101585760003560e01c8063771da5c5116100c3578063b4f94b2e1161007c578063b4f94b2e146103b7578063cae9ca51146103d5578063d505accf14610405578063dd62ed3e14610421578063f2fde38b14610451578063fc4e51f61461046d57610158565b8063771da5c5146102e157806379cc6790146102ff5780637ecebe001461031b5780638da5cb5b1461034b57806395d89b4114610369578063a9059cbb1461038757610158565b8063313ce56711610115578063313ce567146102335780633644e5151461025157806340c10f191461026f57806342966c681461028b57806370a08231146102a7578063715018a6146102d757610158565b806306fdde031461015d578063095ea7b31461017b5780631171bda9146101ab57806318160ddd146101c757806323b872dd146101e557806330adf81f14610215575b600080fd5b610165610489565b6040516101729190612796565b60405180910390f35b61019560048036038101906101909190611ef3565b610517565b6040516101a29190612667565b60405180910390f35b6101c560048036038101906101c09190611fbf565b61052e565b005b6101cf6105da565b6040516101dc9190612a18565b60405180910390f35b6101ff60048036038101906101fa9190611e06565b6105e0565b60405161020c9190612667565b60405180910390f35b61021d6106fa565b60405161022a9190612682565b60405180910390f35b61023b61071e565b6040516102489190612a33565b60405180910390f35b610259610723565b6040516102669190612682565b60405180910390f35b61028960048036038101906102849190611ef3565b610783565b005b6102a560048036038101906102a0919061208e565b610954565b005b6102c160048036038101906102bc9190611da1565b610961565b6040516102ce9190612a18565b60405180910390f35b6102df610979565b005b6102e9610a01565b6040516102f69190612a18565b60405180910390f35b61031960048036038101906103149190611ef3565b610a25565b005b61033560048036038101906103309190611da1565b610b36565b6040516103429190612a18565b60405180910390f35b610353610b4e565b6040516103609190612589565b60405180910390f35b610371610b77565b60405161037e9190612796565b60405180910390f35b6103a1600480360381019061039c9190611ef3565b610c05565b6040516103ae9190612667565b60405180910390f35b6103bf610c1c565b6040516103cc9190612682565b60405180910390f35b6103ef60048036038101906103ea9190611f2f565b610c40565b6040516103fc9190612667565b60405180910390f35b61041f600480360381019061041a9190611e55565b610cd7565b005b61043b60048036038101906104369190611dca565b610fbb565b6040516104489190612a18565b60405180910390f35b61046b60048036038101906104669190611da1565b610fe0565b005b6104876004803603810190610482919061200e565b6110d8565b005b6005805461049690612c56565b80601f01602080910402602001604051908101604052809291908181526020018280546104c290612c56565b801561050f5780601f106104e45761010080835404028352916020019161050f565b820191906000526020600020905b8154815290600101906020018083116104f257829003601f168201915b505050505081565b60006105243384846111ce565b6001905092915050565b610536611399565b73ffffffffffffffffffffffffffffffffffffffff16610554610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146105aa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a190612918565b60405180910390fd5b6105d582828573ffffffffffffffffffffffffffffffffffffffff166113a19092919063ffffffff16565b505050565b60045481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106e357828110156106cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c3906127f8565b60405180910390fd5b6106e2853385846106dd9190612b5d565b6111ce565b5b6106ee858585611427565b60019150509392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60007f0000000000000000000000000000000000000000000000000000000000000000461415610775577f00000000000000000000000000000000000000000000000000000000000000009050610780565b61077d611718565b90505b90565b61078b611399565b73ffffffffffffffffffffffffffffffffffffffff166107a9610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146107ff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107f690612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161086690612998565b60405180910390fd5b61087b600083836117c0565b806004600082825461088d9190612b07565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108e39190612b07565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109489190612a18565b60405180910390a35050565b61095e33826117c5565b50565b60016020528060005260406000206000915090505481565b610981611399565b73ffffffffffffffffffffffffffffffffffffffff1661099f610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146109f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ec90612918565b60405180910390fd5b6109ff600061192b565b565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610b275781811015610b10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b07906128b8565b60405180910390fd5b610b2683338484610b219190612b5d565b6111ce565b5b610b3183836117c5565b505050565b60036020528060005260406000206000915090505481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60068054610b8490612c56565b80601f0160208091040260200160405190810160405280929190818152602001828054610bb090612c56565b8015610bfd5780601f10610bd257610100808354040283529160200191610bfd565b820191906000526020600020905b815481529060010190602001808311610be057829003601f168201915b505050505081565b6000610c12338484611427565b6001905092915050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000610c4c8484610517565b15610ccb578373ffffffffffffffffffffffffffffffffffffffff16638f4ffcb1338530866040518563ffffffff1660e01b8152600401610c90949392919061261b565b600060405180830381600087803b158015610caa57600080fd5b505af1158015610cbe573d6000803e3d6000fd5b5050505060019050610cd0565b600090505b9392505050565b42841015610d1a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1190612938565b60405180910390fd5b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08160001c1115610d80576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d7790612978565b60405180910390fd5b601b8360ff161480610d955750601c8360ff16145b610dd4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dcb90612818565b60405180910390fd5b6000610dde610723565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9898989600360008e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000815480929190610e5290612cb9565b919050558a604051602001610e6c9695949392919061269d565b60405160208183030381529060405280519060200120604051602001610e93929190612552565b604051602081830303815290604052805190602001209050600060018286868660405160008152602001604052604051610ed09493929190612751565b6020604051602081039080840390855afa158015610ef2573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015610f6657508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610fa5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9c90612858565b60405180910390fd5b610fb08989896111ce565b505050505050505050565b6002602052816000526040600020602052806000526040600020600091509150505481565b610fe8611399565b73ffffffffffffffffffffffffffffffffffffffff16611006610b4e565b73ffffffffffffffffffffffffffffffffffffffff161461105c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161105390612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110c3906127d8565b60405180910390fd5b6110d58161192b565b50565b6110e0611399565b73ffffffffffffffffffffffffffffffffffffffff166110fe610b4e565b73ffffffffffffffffffffffffffffffffffffffff1614611154576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114b90612918565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff1663b88d4fde30868686866040518663ffffffff1660e01b81526004016111959594939291906125a4565b600060405180830381600087803b1580156111af57600080fd5b505af11580156111c3573d6000803e3d6000fd5b505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561123e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161123590612898565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156112ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112a5906127b8565b60405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161138c9190612a18565b60405180910390a3505050565b600033905090565b6114228363a9059cbb60e01b84846040516024016113c09291906125f2565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506119ef565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611497576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161148e906129f8565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611507576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fe906129d8565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161156d90612838565b60405180910390fd5b6115818383836117c0565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611608576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ff906128d8565b60405180910390fd5b81816116149190612b5d565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546116a69190612b07565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161170a9190612a18565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600560405161174a919061253b565b60405180910390206040518060400160405280600181526020017f31000000000000000000000000000000000000000000000000000000000000008152508051906020012046306040516020016117a59594939291906126fe565b60405160208183030381529060405280519060200120905090565b505050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561184c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611843906128f8565b60405180910390fd5b611858836000846117c0565b81816118649190612b5d565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600460008282546118b99190612b5d565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161191e9190612a18565b60405180910390a3505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000611a51826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16611ab69092919063ffffffff16565b9050600081511115611ab15780806020019051810190611a719190611f96565b611ab0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611aa7906129b8565b60405180910390fd5b5b505050565b6060611ac58484600085611ace565b90509392505050565b606082471015611b13576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b0a90612878565b60405180910390fd5b611b1c85611be2565b611b5b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5290612958565b60405180910390fd5b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611b849190612524565b60006040518083038185875af1925050503d8060008114611bc1576040519150601f19603f3d011682016040523d82523d6000602084013e611bc6565b606091505b5091509150611bd6828286611bf5565b92505050949350505050565b600080823b905060008111915050919050565b60608315611c0557829050611c55565b600083511115611c185782518084602001fd5b816040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c4c9190612796565b60405180910390fd5b9392505050565b6000611c6f611c6a84612a73565b612a4e565b905082815260208101848484011115611c8757600080fd5b611c92848285612c14565b509392505050565b600081359050611ca981613176565b92915050565b600081519050611cbe8161318d565b92915050565b600081359050611cd3816131a4565b92915050565b60008083601f840112611ceb57600080fd5b8235905067ffffffffffffffff811115611d0457600080fd5b602083019150836001820283011115611d1c57600080fd5b9250929050565b600082601f830112611d3457600080fd5b8135611d44848260208601611c5c565b91505092915050565b600081359050611d5c816131bb565b92915050565b600081359050611d71816131d2565b92915050565b600081359050611d86816131e9565b92915050565b600081359050611d9b81613200565b92915050565b600060208284031215611db357600080fd5b6000611dc184828501611c9a565b91505092915050565b60008060408385031215611ddd57600080fd5b6000611deb85828601611c9a565b9250506020611dfc85828601611c9a565b9150509250929050565b600080600060608486031215611e1b57600080fd5b6000611e2986828701611c9a565b9350506020611e3a86828701611c9a565b9250506040611e4b86828701611d77565b9150509250925092565b600080600080600080600060e0888a031215611e7057600080fd5b6000611e7e8a828b01611c9a565b9750506020611e8f8a828b01611c9a565b9650506040611ea08a828b01611d77565b9550506060611eb18a828b01611d77565b9450506080611ec28a828b01611d8c565b93505060a0611ed38a828b01611cc4565b92505060c0611ee48a828b01611cc4565b91505092959891949750929550565b60008060408385031215611f0657600080fd5b6000611f1485828601611c9a565b9250506020611f2585828601611d77565b9150509250929050565b600080600060608486031215611f4457600080fd5b6000611f5286828701611c9a565b9350506020611f6386828701611d77565b925050604084013567ffffffffffffffff811115611f8057600080fd5b611f8c86828701611d23565b9150509250925092565b600060208284031215611fa857600080fd5b6000611fb684828501611caf565b91505092915050565b600080600060608486031215611fd457600080fd5b6000611fe286828701611d4d565b9350506020611ff386828701611c9a565b925050604061200486828701611d77565b9150509250925092565b60008060008060006080868803121561202657600080fd5b600061203488828901611d62565b955050602061204588828901611c9a565b945050604061205688828901611d77565b935050606086013567ffffffffffffffff81111561207357600080fd5b61207f88828901611cd9565b92509250509295509295909350565b6000602082840312156120a057600080fd5b60006120ae84828501611d77565b91505092915050565b6120c081612b91565b82525050565b6120cf81612ba3565b82525050565b6120de81612baf565b82525050565b6120f56120f082612baf565b612d02565b82525050565b60006121078385612acf565b9350612114838584612c14565b61211d83612d99565b840190509392505050565b600061213382612ab9565b61213d8185612acf565b935061214d818560208601612c23565b61215681612d99565b840191505092915050565b600061216c82612ab9565b6121768185612ae0565b9350612186818560208601612c23565b80840191505092915050565b6000815461219f81612c56565b6121a98186612ae0565b945060018216600081146121c457600181146121d557612208565b60ff19831686528186019350612208565b6121de85612aa4565b60005b83811015612200578154818901526001820191506020810190506121e1565b838801955050505b50505092915050565b600061221c82612ac4565b6122268185612aeb565b9350612236818560208601612c23565b61223f81612d99565b840191505092915050565b6000612257601b83612aeb565b915061226282612daa565b602082019050919050565b600061227a602683612aeb565b915061228582612dd3565b604082019050919050565b600061229d602183612aeb565b91506122a882612e22565b604082019050919050565b60006122c0600283612afc565b91506122cb82612e71565b600282019050919050565b60006122e3601b83612aeb565b91506122ee82612e9a565b602082019050919050565b6000612306601d83612aeb565b915061231182612ec3565b602082019050919050565b6000612329601183612aeb565b915061233482612eec565b602082019050919050565b600061234c602683612aeb565b915061235782612f15565b604082019050919050565b600061236f601d83612aeb565b915061237a82612f64565b602082019050919050565b6000612392601d83612aeb565b915061239d82612f8d565b602082019050919050565b60006123b5601f83612aeb565b91506123c082612fb6565b602082019050919050565b60006123d8601b83612aeb565b91506123e382612fdf565b602082019050919050565b60006123fb602083612aeb565b915061240682613008565b602082019050919050565b600061241e601283612aeb565b915061242982613031565b602082019050919050565b6000612441601d83612aeb565b915061244c8261305a565b602082019050919050565b6000612464601b83612aeb565b915061246f82613083565b602082019050919050565b6000612487601883612aeb565b9150612492826130ac565b602082019050919050565b60006124aa602a83612aeb565b91506124b5826130d5565b604082019050919050565b60006124cd601c83612aeb565b91506124d882613124565b602082019050919050565b60006124f0601e83612aeb565b91506124fb8261314d565b602082019050919050565b61250f81612bfd565b82525050565b61251e81612c07565b82525050565b60006125308284612161565b915081905092915050565b60006125478284612192565b915081905092915050565b600061255d826122b3565b915061256982856120e4565b60208201915061257982846120e4565b6020820191508190509392505050565b600060208201905061259e60008301846120b7565b92915050565b60006080820190506125b960008301886120b7565b6125c660208301876120b7565b6125d36040830186612506565b81810360608301526125e68184866120fb565b90509695505050505050565b600060408201905061260760008301856120b7565b6126146020830184612506565b9392505050565b600060808201905061263060008301876120b7565b61263d6020830186612506565b61264a60408301856120b7565b818103606083015261265c8184612128565b905095945050505050565b600060208201905061267c60008301846120c6565b92915050565b600060208201905061269760008301846120d5565b92915050565b600060c0820190506126b260008301896120d5565b6126bf60208301886120b7565b6126cc60408301876120b7565b6126d96060830186612506565b6126e66080830185612506565b6126f360a0830184612506565b979650505050505050565b600060a08201905061271360008301886120d5565b61272060208301876120d5565b61272d60408301866120d5565b61273a6060830185612506565b61274760808301846120b7565b9695505050505050565b600060808201905061276660008301876120d5565b6127736020830186612515565b61278060408301856120d5565b61278d60608301846120d5565b95945050505050565b600060208201905081810360008301526127b08184612211565b905092915050565b600060208201905081810360008301526127d18161224a565b9050919050565b600060208201905081810360008301526127f18161226d565b9050919050565b6000602082019050818103600083015261281181612290565b9050919050565b60006020820190508181036000830152612831816122d6565b9050919050565b60006020820190508181036000830152612851816122f9565b9050919050565b600060208201905081810360008301526128718161231c565b9050919050565b600060208201905081810360008301526128918161233f565b9050919050565b600060208201905081810360008301526128b181612362565b9050919050565b600060208201905081810360008301526128d181612385565b9050919050565b600060208201905081810360008301526128f1816123a8565b9050919050565b60006020820190508181036000830152612911816123cb565b9050919050565b60006020820190508181036000830152612931816123ee565b9050919050565b6000602082019050818103600083015261295181612411565b9050919050565b6000602082019050818103600083015261297181612434565b9050919050565b6000602082019050818103600083015261299181612457565b9050919050565b600060208201905081810360008301526129b18161247a565b9050919050565b600060208201905081810360008301526129d18161249d565b9050919050565b600060208201905081810360008301526129f1816124c0565b9050919050565b60006020820190508181036000830152612a11816124e3565b9050919050565b6000602082019050612a2d6000830184612506565b92915050565b6000602082019050612a486000830184612515565b92915050565b6000612a58612a69565b9050612a648282612c88565b919050565b6000604051905090565b600067ffffffffffffffff821115612a8e57612a8d612d6a565b5b612a9782612d99565b9050602081019050919050565b60008190508160005260206000209050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b600081905092915050565b6000612b1282612bfd565b9150612b1d83612bfd565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115612b5257612b51612d0c565b5b828201905092915050565b6000612b6882612bfd565b9150612b7383612bfd565b925082821015612b8657612b85612d0c565b5b828203905092915050565b6000612b9c82612bdd565b9050919050565b60008115159050919050565b6000819050919050565b6000612bc482612b91565b9050919050565b6000612bd682612b91565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b83811015612c41578082015181840152602081019050612c26565b83811115612c50576000848401525b50505050565b60006002820490506001821680612c6e57607f821691505b60208210811415612c8257612c81612d3b565b5b50919050565b612c9182612d99565b810181811067ffffffffffffffff82111715612cb057612caf612d6a565b5b80604052505050565b6000612cc482612bfd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415612cf757612cf6612d0c565b5b600182019050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f417070726f766520746f20746865207a65726f20616464726573730000000000600082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b7f1901000000000000000000000000000000000000000000000000000000000000600082015250565b7f496e76616c6964207369676e6174757265202776272076616c75650000000000600082015250565b7f5472616e7366657220746f2074686520746f6b656e2061646472657373000000600082015250565b7f496e76616c6964207369676e6174757265000000000000000000000000000000600082015250565b7f416464726573733a20696e73756666696369656e742062616c616e636520666f60008201527f722063616c6c0000000000000000000000000000000000000000000000000000602082015250565b7f417070726f76652066726f6d20746865207a65726f2061646472657373000000600082015250565b7f4275726e20616d6f756e74206578636565647320616c6c6f77616e6365000000600082015250565b7f5472616e7366657220616d6f756e7420657863656564732062616c616e636500600082015250565b7f4275726e20616d6f756e7420657863656564732062616c616e63650000000000600082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f5065726d697373696f6e20657870697265640000000000000000000000000000600082015250565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b7f496e76616c6964207369676e6174757265202773272076616c75650000000000600082015250565b7f4d696e7420746f20746865207a65726f20616464726573730000000000000000600082015250565b7f5361666545524332303a204552433230206f7065726174696f6e20646964206e60008201527f6f74207375636365656400000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220746f20746865207a65726f206164647265737300000000600082015250565b7f5472616e736665722066726f6d20746865207a65726f20616464726573730000600082015250565b61317f81612b91565b811461318a57600080fd5b50565b61319681612ba3565b81146131a157600080fd5b50565b6131ad81612baf565b81146131b857600080fd5b50565b6131c481612bb9565b81146131cf57600080fd5b50565b6131db81612bcb565b81146131e657600080fd5b50565b6131f281612bfd565b81146131fd57600080fd5b50565b61320981612c07565b811461321457600080fd5b5056fea2646970667358221220b66e3ef21ffde9263f6eb3247e39c2adf91afb2daa59dd552c43bf8eace893db64736f6c63430008040033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101585760003560e01c8063771da5c5116100c3578063b4f94b2e1161007c578063b4f94b2e146103b7578063cae9ca51146103d5578063d505accf14610405578063dd62ed3e14610421578063f2fde38b14610451578063fc4e51f61461046d57610158565b8063771da5c5146102e157806379cc6790146102ff5780637ecebe001461031b5780638da5cb5b1461034b57806395d89b4114610369578063a9059cbb1461038757610158565b8063313ce56711610115578063313ce567146102335780633644e5151461025157806340c10f191461026f57806342966c681461028b57806370a08231146102a7578063715018a6146102d757610158565b806306fdde031461015d578063095ea7b31461017b5780631171bda9146101ab57806318160ddd146101c757806323b872dd146101e557806330adf81f14610215575b600080fd5b610165610489565b6040516101729190612796565b60405180910390f35b61019560048036038101906101909190611ef3565b610517565b6040516101a29190612667565b60405180910390f35b6101c560048036038101906101c09190611fbf565b61052e565b005b6101cf6105da565b6040516101dc9190612a18565b60405180910390f35b6101ff60048036038101906101fa9190611e06565b6105e0565b60405161020c9190612667565b60405180910390f35b61021d6106fa565b60405161022a9190612682565b60405180910390f35b61023b61071e565b6040516102489190612a33565b60405180910390f35b610259610723565b6040516102669190612682565b60405180910390f35b61028960048036038101906102849190611ef3565b610783565b005b6102a560048036038101906102a0919061208e565b610954565b005b6102c160048036038101906102bc9190611da1565b610961565b6040516102ce9190612a18565b60405180910390f35b6102df610979565b005b6102e9610a01565b6040516102f69190612a18565b60405180910390f35b61031960048036038101906103149190611ef3565b610a25565b005b61033560048036038101906103309190611da1565b610b36565b6040516103429190612a18565b60405180910390f35b610353610b4e565b6040516103609190612589565b60405180910390f35b610371610b77565b60405161037e9190612796565b60405180910390f35b6103a1600480360381019061039c9190611ef3565b610c05565b6040516103ae9190612667565b60405180910390f35b6103bf610c1c565b6040516103cc9190612682565b60405180910390f35b6103ef60048036038101906103ea9190611f2f565b610c40565b6040516103fc9190612667565b60405180910390f35b61041f600480360381019061041a9190611e55565b610cd7565b005b61043b60048036038101906104369190611dca565b610fbb565b6040516104489190612a18565b60405180910390f35b61046b60048036038101906104669190611da1565b610fe0565b005b6104876004803603810190610482919061200e565b6110d8565b005b6005805461049690612c56565b80601f01602080910402602001604051908101604052809291908181526020018280546104c290612c56565b801561050f5780601f106104e45761010080835404028352916020019161050f565b820191906000526020600020905b8154815290600101906020018083116104f257829003601f168201915b505050505081565b60006105243384846111ce565b6001905092915050565b610536611399565b73ffffffffffffffffffffffffffffffffffffffff16610554610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146105aa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105a190612918565b60405180910390fd5b6105d582828573ffffffffffffffffffffffffffffffffffffffff166113a19092919063ffffffff16565b505050565b60045481565b600080600260008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106e357828110156106cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106c3906127f8565b60405180910390fd5b6106e2853385846106dd9190612b5d565b6111ce565b5b6106ee858585611427565b60019150509392505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b601281565b60007f0000000000000000000000000000000000000000000000000000000000000000461415610775577f00000000000000000000000000000000000000000000000000000000000000009050610780565b61077d611718565b90505b90565b61078b611399565b73ffffffffffffffffffffffffffffffffffffffff166107a9610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146107ff576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107f690612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161086690612998565b60405180910390fd5b61087b600083836117c0565b806004600082825461088d9190612b07565b9250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546108e39190612b07565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109489190612a18565b60405180910390a35050565b61095e33826117c5565b50565b60016020528060005260406000206000915090505481565b610981611399565b73ffffffffffffffffffffffffffffffffffffffff1661099f610b4e565b73ffffffffffffffffffffffffffffffffffffffff16146109f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ec90612918565b60405180910390fd5b6109ff600061192b565b565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610b275781811015610b10576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b07906128b8565b60405180910390fd5b610b2683338484610b219190612b5d565b6111ce565b5b610b3183836117c5565b505050565b60036020528060005260406000206000915090505481565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60068054610b8490612c56565b80601f0160208091040260200160405190810160405280929190818152602001828054610bb090612c56565b8015610bfd5780601f10610bd257610100808354040283529160200191610bfd565b820191906000526020600020905b815481529060010190602001808311610be057829003601f168201915b505050505081565b6000610c12338484611427565b6001905092915050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000610c4c8484610517565b15610ccb578373ffffffffffffffffffffffffffffffffffffffff16638f4ffcb1338530866040518563ffffffff1660e01b8152600401610c90949392919061261b565b600060405180830381600087803b158015610caa57600080fd5b505af1158015610cbe573d6000803e3d6000fd5b5050505060019050610cd0565b600090505b9392505050565b42841015610d1a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1190612938565b60405180910390fd5b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08160001c1115610d80576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d7790612978565b60405180910390fd5b601b8360ff161480610d955750601c8360ff16145b610dd4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dcb90612818565b60405180910390fd5b6000610dde610723565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9898989600360008e73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000815480929190610e5290612cb9565b919050558a604051602001610e6c9695949392919061269d565b60405160208183030381529060405280519060200120604051602001610e93929190612552565b604051602081830303815290604052805190602001209050600060018286868660405160008152602001604052604051610ed09493929190612751565b6020604051602081039080840390855afa158015610ef2573d6000803e3d6000fd5b505050602060405103519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015610f6657508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610fa5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9c90612858565b60405180910390fd5b610fb08989896111ce565b505050505050505050565b6002602052816000526040600020602052806000526040600020600091509150505481565b610fe8611399565b73ffffffffffffffffffffffffffffffffffffffff16611006610b4e565b73ffffffffffffffffffffffffffffffffffffffff161461105c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161105390612918565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110c3906127d8565b60405180910390fd5b6110d58161192b565b50565b6110e0611399565b73ffffffffffffffffffffffffffffffffffffffff166110fe610b4e565b73ffffffffffffffffffffffffffffffffffffffff1614611154576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114b90612918565b60405180910390fd5b8473ffffffffffffffffffffffffffffffffffffffff1663b88d4fde30868686866040518663ffffffff1660e01b81526004016111959594939291906125a4565b600060405180830381600087803b1580156111af57600080fd5b505af11580156111c3573d6000803e3d6000fd5b505050505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561123e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161123590612898565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156112ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112a5906127b8565b60405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258360405161138c9190612a18565b60405180910390a3505050565b600033905090565b6114228363a9059cbb60e01b84846040516024016113c09291906125f2565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506119ef565b505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611497576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161148e906129f8565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611507576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114fe906129d8565b60405180910390fd5b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161156d90612838565b60405180910390fd5b6115818383836117c0565b6000600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611608576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ff906128d8565b60405180910390fd5b81816116149190612b5d565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546116a69190612b07565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161170a9190612a18565b60405180910390a350505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f600560405161174a919061253b565b60405180910390206040518060400160405280600181526020017f31000000000000000000000000000000000000000000000000000000000000008152508051906020012046306040516020016117a59594939291906126fe565b60405160208183030381529060405280519060200120905090565b505050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561184c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611843906128f8565b60405180910390fd5b611858836000846117c0565b81816118649190612b5d565b600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600460008282546118b99190612b5d565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161191e9190612a18565b60405180910390a3505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b6000611a51826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16611ab69092919063ffffffff16565b9050600081511115611ab15780806020019051810190611a719190611f96565b611ab0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611aa7906129b8565b60405180910390fd5b5b505050565b6060611ac58484600085611ace565b90509392505050565b606082471015611b13576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b0a90612878565b60405180910390fd5b611b1c85611be2565b611b5b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b5290612958565b60405180910390fd5b6000808673ffffffffffffffffffffffffffffffffffffffff168587604051611b849190612524565b60006040518083038185875af1925050503d8060008114611bc1576040519150601f19603f3d011682016040523d82523d6000602084013e611bc6565b606091505b5091509150611bd6828286611bf5565b92505050949350505050565b600080823b905060008111915050919050565b60608315611c0557829050611c55565b600083511115611c185782518084602001fd5b816040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c4c9190612796565b60405180910390fd5b9392505050565b6000611c6f611c6a84612a73565b612a4e565b905082815260208101848484011115611c8757600080fd5b611c92848285612c14565b509392505050565b600081359050611ca981613176565b92915050565b600081519050611cbe8161318d565b92915050565b600081359050611cd3816131a4565b92915050565b60008083601f840112611ceb57600080fd5b8235905067ffffffffffffffff811115611d0457600080fd5b602083019150836001820283011115611d1c57600080fd5b9250929050565b600082601f830112611d3457600080fd5b8135611d44848260208601611c5c565b91505092915050565b600081359050611d5c816131bb565b92915050565b600081359050611d71816131d2565b92915050565b600081359050611d86816131e9565b92915050565b600081359050611d9b81613200565b92915050565b600060208284031215611db357600080fd5b6000611dc184828501611c9a565b91505092915050565b60008060408385031215611ddd57600080fd5b6000611deb85828601611c9a565b9250506020611dfc85828601611c9a565b9150509250929050565b600080600060608486031215611e1b57600080fd5b6000611e2986828701611c9a565b9350506020611e3a86828701611c9a565b9250506040611e4b86828701611d77565b9150509250925092565b600080600080600080600060e0888a031215611e7057600080fd5b6000611e7e8a828b01611c9a565b9750506020611e8f8a828b01611c9a565b9650506040611ea08a828b01611d77565b9550506060611eb18a828b01611d77565b9450506080611ec28a828b01611d8c565b93505060a0611ed38a828b01611cc4565b92505060c0611ee48a828b01611cc4565b91505092959891949750929550565b60008060408385031215611f0657600080fd5b6000611f1485828601611c9a565b9250506020611f2585828601611d77565b9150509250929050565b600080600060608486031215611f4457600080fd5b6000611f5286828701611c9a565b9350506020611f6386828701611d77565b925050604084013567ffffffffffffffff811115611f8057600080fd5b611f8c86828701611d23565b9150509250925092565b600060208284031215611fa857600080fd5b6000611fb684828501611caf565b91505092915050565b600080600060608486031215611fd457600080fd5b6000611fe286828701611d4d565b9350506020611ff386828701611c9a565b925050604061200486828701611d77565b9150509250925092565b60008060008060006080868803121561202657600080fd5b600061203488828901611d62565b955050602061204588828901611c9a565b945050604061205688828901611d77565b935050606086013567ffffffffffffffff81111561207357600080fd5b61207f88828901611cd9565b92509250509295509295909350565b6000602082840312156120a057600080fd5b60006120ae84828501611d77565b91505092915050565b6120c081612b91565b82525050565b6120cf81612ba3565b82525050565b6120de81612baf565b82525050565b6120f56120f082612baf565b612d02565b82525050565b60006121078385612acf565b9350612114838584612c14565b61211d83612d99565b840190509392505050565b600061213382612ab9565b61213d8185612acf565b935061214d818560208601612c23565b61215681612d99565b840191505092915050565b600061216c82612ab9565b6121768185612ae0565b9350612186818560208601612c23565b80840191505092915050565b6000815461219f81612c56565b6121a98186612ae0565b945060018216600081146121c457600181146121d557612208565b60ff19831686528186019350612208565b6121de85612aa4565b60005b83811015612200578154818901526001820191506020810190506121e1565b838801955050505b50505092915050565b600061221c82612ac4565b6122268185612aeb565b9350612236818560208601612c23565b61223f81612d99565b840191505092915050565b6000612257601b83612aeb565b915061226282612daa565b602082019050919050565b600061227a602683612aeb565b915061228582612dd3565b604082019050919050565b600061229d602183612aeb565b91506122a882612e22565b604082019050919050565b60006122c0600283612afc565b91506122cb82612e71565b600282019050919050565b60006122e3601b83612aeb565b91506122ee82612e9a565b602082019050919050565b6000612306601d83612aeb565b915061231182612ec3565b602082019050919050565b6000612329601183612aeb565b915061233482612eec565b602082019050919050565b600061234c602683612aeb565b915061235782612f15565b604082019050919050565b600061236f601d83612aeb565b915061237a82612f64565b602082019050919050565b6000612392601d83612aeb565b915061239d82612f8d565b602082019050919050565b60006123b5601f83612aeb565b91506123c082612fb6565b602082019050919050565b60006123d8601b83612aeb565b91506123e382612fdf565b602082019050919050565b60006123fb602083612aeb565b915061240682613008565b602082019050919050565b600061241e601283612aeb565b915061242982613031565b602082019050919050565b6000612441601d83612aeb565b915061244c8261305a565b602082019050919050565b6000612464601b83612aeb565b915061246f82613083565b602082019050919050565b6000612487601883612aeb565b9150612492826130ac565b602082019050919050565b60006124aa602a83612aeb565b91506124b5826130d5565b604082019050919050565b60006124cd601c83612aeb565b91506124d882613124565b602082019050919050565b60006124f0601e83612aeb565b91506124fb8261314d565b602082019050919050565b61250f81612bfd565b82525050565b61251e81612c07565b82525050565b60006125308284612161565b915081905092915050565b60006125478284612192565b915081905092915050565b600061255d826122b3565b915061256982856120e4565b60208201915061257982846120e4565b6020820191508190509392505050565b600060208201905061259e60008301846120b7565b92915050565b60006080820190506125b960008301886120b7565b6125c660208301876120b7565b6125d36040830186612506565b81810360608301526125e68184866120fb565b90509695505050505050565b600060408201905061260760008301856120b7565b6126146020830184612506565b9392505050565b600060808201905061263060008301876120b7565b61263d6020830186612506565b61264a60408301856120b7565b818103606083015261265c8184612128565b905095945050505050565b600060208201905061267c60008301846120c6565b92915050565b600060208201905061269760008301846120d5565b92915050565b600060c0820190506126b260008301896120d5565b6126bf60208301886120b7565b6126cc60408301876120b7565b6126d96060830186612506565b6126e66080830185612506565b6126f360a0830184612506565b979650505050505050565b600060a08201905061271360008301886120d5565b61272060208301876120d5565b61272d60408301866120d5565b61273a6060830185612506565b61274760808301846120b7565b9695505050505050565b600060808201905061276660008301876120d5565b6127736020830186612515565b61278060408301856120d5565b61278d60608301846120d5565b95945050505050565b600060208201905081810360008301526127b08184612211565b905092915050565b600060208201905081810360008301526127d18161224a565b9050919050565b600060208201905081810360008301526127f18161226d565b9050919050565b6000602082019050818103600083015261281181612290565b9050919050565b60006020820190508181036000830152612831816122d6565b9050919050565b60006020820190508181036000830152612851816122f9565b9050919050565b600060208201905081810360008301526128718161231c565b9050919050565b600060208201905081810360008301526128918161233f565b9050919050565b600060208201905081810360008301526128b181612362565b9050919050565b600060208201905081810360008301526128d181612385565b9050919050565b600060208201905081810360008301526128f1816123a8565b9050919050565b60006020820190508181036000830152612911816123cb565b9050919050565b60006020820190508181036000830152612931816123ee565b9050919050565b6000602082019050818103600083015261295181612411565b9050919050565b6000602082019050818103600083015261297181612434565b9050919050565b6000602082019050818103600083015261299181612457565b9050919050565b600060208201905081810360008301526129b18161247a565b9050919050565b600060208201905081810360008301526129d18161249d565b9050919050565b600060208201905081810360008301526129f1816124c0565b9050919050565b60006020820190508181036000830152612a11816124e3565b9050919050565b6000602082019050612a2d6000830184612506565b92915050565b6000602082019050612a486000830184612515565b92915050565b6000612a58612a69565b9050612a648282612c88565b919050565b6000604051905090565b600067ffffffffffffffff821115612a8e57612a8d612d6a565b5b612a9782612d99565b9050602081019050919050565b60008190508160005260206000209050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b600082825260208201905092915050565b600081905092915050565b6000612b1282612bfd565b9150612b1d83612bfd565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115612b5257612b51612d0c565b5b828201905092915050565b6000612b6882612bfd565b9150612b7383612bfd565b925082821015612b8657612b85612d0c565b5b828203905092915050565b6000612b9c82612bdd565b9050919050565b60008115159050919050565b6000819050919050565b6000612bc482612b91565b9050919050565b6000612bd682612b91565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b83811015612c41578082015181840152602081019050612c26565b83811115612c50576000848401525b50505050565b60006002820490506001821680612c6e57607f821691505b60208210811415612c8257612c81612d3b565b5b50919050565b612c9182612d99565b810181811067ffffffffffffffff82111715612cb057612caf612d6a565b5b80604052505050565b6000612cc482612bfd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415612cf757612cf6612d0c565b5b600182019050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b7f417070726f766520746f20746865207a65726f20616464726573730000000000600082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220616d6f756e74206578636565647320616c6c6f77616e6360008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b7f1901000000000000000000000000000000000000000000000000000000000000600082015250565b7f496e76616c6964207369676e6174757265202776272076616c75650000000000600082015250565b7f5472616e7366657220746f2074686520746f6b656e2061646472657373000000600082015250565b7f496e76616c6964207369676e6174757265000000000000000000000000000000600082015250565b7f416464726573733a20696e73756666696369656e742062616c616e636520666f60008201527f722063616c6c0000000000000000000000000000000000000000000000000000602082015250565b7f417070726f76652066726f6d20746865207a65726f2061646472657373000000600082015250565b7f4275726e20616d6f756e74206578636565647320616c6c6f77616e6365000000600082015250565b7f5472616e7366657220616d6f756e7420657863656564732062616c616e636500600082015250565b7f4275726e20616d6f756e7420657863656564732062616c616e63650000000000600082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f5065726d697373696f6e20657870697265640000000000000000000000000000600082015250565b7f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000600082015250565b7f496e76616c6964207369676e6174757265202773272076616c75650000000000600082015250565b7f4d696e7420746f20746865207a65726f20616464726573730000000000000000600082015250565b7f5361666545524332303a204552433230206f7065726174696f6e20646964206e60008201527f6f74207375636365656400000000000000000000000000000000000000000000602082015250565b7f5472616e7366657220746f20746865207a65726f206164647265737300000000600082015250565b7f5472616e736665722066726f6d20746865207a65726f20616464726573730000600082015250565b61317f81612b91565b811461318a57600080fd5b50565b61319681612ba3565b81146131a157600080fd5b50565b6131ad81612baf565b81146131b857600080fd5b50565b6131c481612bb9565b81146131cf57600080fd5b50565b6131db81612bcb565b81146131e657600080fd5b50565b6131f281612bfd565b81146131fd57600080fd5b50565b61320981612c07565b811461321457600080fd5b5056fea2646970667358221220b66e3ef21ffde9263f6eb3247e39c2adf91afb2daa59dd552c43bf8eace893db64736f6c63430008040033", + "devdoc": { + "kind": "dev", + "methods": { + "approve(address,uint256)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance. Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729", + "returns": { + "_0": "True if the operation succeeded." + } + }, + "approveAndCall(address,uint256,bytes)": { + "details": "If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance.", + "returns": { + "_0": "True if both approval and `receiveApproval` calls succeeded." + } + }, + "burn(uint256)": { + "details": "Requirements: - the caller must have a balance of at least `amount`." + }, + "burnFrom(address,uint256)": { + "details": "Requirements: - `account` must have a balance of at least `amount`, - the caller must have allowance for `account`'s tokens of at least `amount`." + }, + "mint(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address." + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "details": "The deadline argument can be set to `type(uint256).max to create permits that effectively never expire. If the `amount` is set to `type(uint256).max` then `transferFrom` and `burnFrom` will not reduce an allowance." + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "transfer(address,uint256)": { + "details": "Requirements: - `recipient` cannot be the zero address, - the caller must have a balance of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferFrom(address,address,uint256)": { + "details": "Requirements: - `sender` and `recipient` cannot be the zero address, - `sender` must have a balance of at least `amount`, - the caller must have allowance for `sender`'s tokens of at least `amount`.", + "returns": { + "_0": "True if the operation succeeded, reverts otherwise." + } + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "DOMAIN_SEPARATOR()": { + "notice": "Returns hash of EIP712 Domain struct with the token name as a signing domain and token contract as a verifying contract. Used to construct EIP2612 signature provided to `permit` function." + }, + "PERMIT_TYPEHASH()": { + "notice": "Returns EIP2612 Permit message hash. Used to construct EIP2612 signature provided to `permit` function." + }, + "allowance(address,address)": { + "notice": "The remaining number of tokens that spender will be allowed to spend on behalf of owner through `transferFrom` and `burnFrom`. This is zero by default." + }, + "approve(address,uint256)": { + "notice": "Sets `amount` as the allowance of `spender` over the caller's tokens." + }, + "approveAndCall(address,uint256,bytes)": { + "notice": "Calls `receiveApproval` function on spender previously approving the spender to withdraw from the caller multiple times, up to the `amount` amount. If this function is called again, it overwrites the current allowance with `amount`. Reverts if the approval reverted or if `receiveApproval` call on the spender reverted." + }, + "balanceOf(address)": { + "notice": "The amount of tokens owned by the given account." + }, + "burn(uint256)": { + "notice": "Destroys `amount` tokens from the caller." + }, + "burnFrom(address,uint256)": { + "notice": "Destroys `amount` of tokens from `account` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + }, + "decimals()": { + "notice": "The decimals places of the token." + }, + "mint(address,uint256)": { + "notice": "Creates `amount` tokens and assigns them to `account`, increasing the total supply." + }, + "name()": { + "notice": "The name of the token." + }, + "nonces(address)": { + "notice": "Returns the current nonce for EIP2612 permission for the provided token owner for a replay protection. Used to construct EIP2612 signature provided to `permit` function." + }, + "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { + "notice": "EIP2612 approval made with secp256k1 signature. Users can authorize a transfer of their tokens with a signature conforming EIP712 standard, rather than an on-chain transaction from their address. Anyone can submit this signature on the user's behalf by calling the permit function, paying gas fees, and possibly performing other actions in the same transaction." + }, + "symbol()": { + "notice": "The symbol of the token." + }, + "totalSupply()": { + "notice": "The amount of tokens in existence." + }, + "transfer(address,uint256)": { + "notice": "Moves `amount` tokens from the caller's account to `recipient`." + }, + "transferFrom(address,address,uint256)": { + "notice": "Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance unless the allowance was made for `type(uint256).max`." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 7, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 1981, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "balanceOf", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 1989, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "allowance", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_mapping(t_address,t_uint256))" + }, + { + "astId": 1995, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "nonces", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 2010, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "totalSupply", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 2014, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "name", + "offset": 0, + "slot": "5", + "type": "t_string_storage" + }, + { + "astId": 2018, + "contract": "contracts/token/TBTC.sol:TBTC", + "label": "symbol", + "offset": 0, + "slot": "6", + "type": "t_string_storage" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_mapping(t_address,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_string_storage": { + "encoding": "bytes", + "label": "string", + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} diff --git a/typescript/src/lib/ethereum/artifacts/mainnet/TBTCVault.json b/typescript/src/lib/ethereum/artifacts/mainnet/TBTCVault.json new file mode 100644 index 000000000..2bb682a67 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/mainnet/TBTCVault.json @@ -0,0 +1,1606 @@ +{ + "address": "0x9C070027cdC9dc8F82416B2e5314E11DFb4FE3CD", + "abi": [ + { + "inputs": [ + { + "internalType": "contract Bank", + "name": "_bank", + "type": "address" + }, + { + "internalType": "contract TBTC", + "name": "_tbtcToken", + "type": "address" + }, + { + "internalType": "contract Bridge", + "name": "_bridge", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "GuardianAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "GuardianRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Minted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "MinterAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "MinterRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "guardian", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + } + ], + "name": "OptimisticMintingCancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "optimisticMintingDebt", + "type": "uint256" + } + ], + "name": "OptimisticMintingDebtRepaid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingDelay", + "type": "uint32" + } + ], + "name": "OptimisticMintingDelayUpdateStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingDelay", + "type": "uint32" + } + ], + "name": "OptimisticMintingDelayUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingFeeDivisor", + "type": "uint32" + } + ], + "name": "OptimisticMintingFeeUpdateStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint32", + "name": "newOptimisticMintingFeeDivisor", + "type": "uint32" + } + ], + "name": "OptimisticMintingFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "optimisticMintingDebt", + "type": "uint256" + } + ], + "name": "OptimisticMintingFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "OptimisticMintingPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "minter", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "depositKey", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "depositor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "OptimisticMintingRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "OptimisticMintingUnpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Unminted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newVault", + "type": "address" + } + ], + "name": "UpgradeFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newVault", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "UpgradeInitiated", + "type": "event" + }, + { + "inputs": [], + "name": "GOVERNANCE_DELAY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SATOSHI_MULTIPLIER", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "addGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "addMinter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "amountToSatoshis", + "outputs": [ + { + "internalType": "uint256", + "name": "convertibleAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "remainder", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "satoshis", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bank", + "outputs": [ + { + "internalType": "contract Bank", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_newOptimisticMintingDelay", + "type": "uint32" + } + ], + "name": "beginOptimisticMintingDelayUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_newOptimisticMintingFeeDivisor", + "type": "uint32" + } + ], + "name": "beginOptimisticMintingFeeUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "bridge", + "outputs": [ + { + "internalType": "contract Bridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "calculateDepositKey", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "cancelOptimisticMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "finalizeOptimisticMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeOptimisticMintingDelayUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeOptimisticMintingFeeUpdate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finalizeUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getMinters", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newVault", + "type": "address" + } + ], + "name": "initiateUpgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isGuardian", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isMinter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isOptimisticMintingPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "minters", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newOptimisticMintingDelay", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newOptimisticMintingFeeDivisor", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "optimisticMintingDebt", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingDelay", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingDelayUpdateInitiatedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingFeeDivisor", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "optimisticMintingFeeUpdateInitiatedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "optimisticMintingRequests", + "outputs": [ + { + "internalType": "uint64", + "name": "requestedAt", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "finalizedAt", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauseOptimisticMinting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "receiveApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "satoshis", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "receiveBalanceApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "depositors", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "depositedSatoshiAmounts", + "type": "uint256[]" + } + ], + "name": "receiveBalanceIncrease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "recoverERC20FromToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC721", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "recoverERC721FromToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "guardian", + "type": "address" + } + ], + "name": "removeGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "minter", + "type": "address" + } + ], + "name": "removeMinter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "fundingTxHash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fundingOutputIndex", + "type": "uint32" + } + ], + "name": "requestOptimisticMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "tbtcToken", + "outputs": [ + { + "internalType": "contract TBTC", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "unmint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "redemptionData", + "type": "bytes" + } + ], + "name": "unmintAndRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseOptimisticMinting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeInitiatedTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x328b5ea0d524480a7d27a2b1c04906927341b9b28134919001c6ed53b6c00083", + "receipt": { + "to": null, + "from": "0x123694886DBf5Ac94DDA07135349534536D14cAf", + "contractAddress": "0x9C070027cdC9dc8F82416B2e5314E11DFb4FE3CD", + "transactionIndex": 115, + "gasUsed": "3340772", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000000000000000080000000000000000000000000000000000400200000000000000000000000000000000001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000020000000000000000000000000000000000000400000000000000000000000000000", + "blockHash": "0xdf461e4baea5c76afa03ae1d580c98c2494ff97204c3e5f1ce324ead8ce02fc4", + "transactionHash": "0x328b5ea0d524480a7d27a2b1c04906927341b9b28134919001c6ed53b6c00083", + "logs": [ + { + "transactionIndex": 115, + "blockNumber": 16472741, + "transactionHash": "0x328b5ea0d524480a7d27a2b1c04906927341b9b28134919001c6ed53b6c00083", + "address": "0x9C070027cdC9dc8F82416B2e5314E11DFb4FE3CD", + "topics": [ + "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000123694886dbf5ac94dda07135349534536d14caf" + ], + "data": "0x", + "logIndex": 233, + "blockHash": "0xdf461e4baea5c76afa03ae1d580c98c2494ff97204c3e5f1ce324ead8ce02fc4" + } + ], + "blockNumber": 16472741, + "cumulativeGasUsed": "14998097", + "status": 1, + "byzantium": true + }, + "args": [ + "0x65Fbae61ad2C8836fFbFB502A0dA41b0789D9Fc6", + "0x18084fbA666a33d37592fA2633fD49a74DD93a88", + "0x5e4861a80B55f035D899f66772117F00FA0E8e7B" + ], + "numDeployments": 2, + "solcInputHash": "802132f7da69a8a4226cb9424480847b", + "metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"contract Bank\",\"name\":\"_bank\",\"type\":\"address\"},{\"internalType\":\"contract TBTC\",\"name\":\"_tbtcToken\",\"type\":\"address\"},{\"internalType\":\"contract Bridge\",\"name\":\"_bridge\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"GuardianAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"GuardianRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Minted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"MinterAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"MinterRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositKey\",\"type\":\"uint256\"}],\"name\":\"OptimisticMintingCancelled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticMintingDebt\",\"type\":\"uint256\"}],\"name\":\"OptimisticMintingDebtRepaid\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingDelay\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingDelayUpdateStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingDelay\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingDelayUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingFeeDivisor\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingFeeUpdateStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"newOptimisticMintingFeeDivisor\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingFeeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositKey\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticMintingDebt\",\"type\":\"uint256\"}],\"name\":\"OptimisticMintingFinalized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"OptimisticMintingPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"depositKey\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"depositor\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"OptimisticMintingRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"OptimisticMintingUnpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Unminted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newVault\",\"type\":\"address\"}],\"name\":\"UpgradeFinalized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newVault\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"UpgradeInitiated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"GOVERNANCE_DELAY\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SATOSHI_MULTIPLIER\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"addGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"addMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"amountToSatoshis\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"convertibleAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"remainder\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"satoshis\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bank\",\"outputs\":[{\"internalType\":\"contract Bank\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_newOptimisticMintingDelay\",\"type\":\"uint32\"}],\"name\":\"beginOptimisticMintingDelayUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_newOptimisticMintingFeeDivisor\",\"type\":\"uint32\"}],\"name\":\"beginOptimisticMintingFeeUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bridge\",\"outputs\":[{\"internalType\":\"contract Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"calculateDepositKey\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"cancelOptimisticMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"finalizeOptimisticMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"finalizeOptimisticMintingDelayUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"finalizeOptimisticMintingFeeUpdate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"finalizeUpgrade\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getMinters\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newVault\",\"type\":\"address\"}],\"name\":\"initiateUpgrade\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isGuardian\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"isMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isOptimisticMintingPaused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"minters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newOptimisticMintingDelay\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newOptimisticMintingFeeDivisor\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newVault\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"optimisticMintingDebt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingDelay\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingDelayUpdateInitiatedTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingFeeDivisor\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticMintingFeeUpdateInitiatedTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"optimisticMintingRequests\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"requestedAt\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"finalizedAt\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauseOptimisticMinting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"extraData\",\"type\":\"bytes\"}],\"name\":\"receiveApproval\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"satoshis\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"name\":\"receiveBalanceApproval\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"depositors\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"depositedSatoshiAmounts\",\"type\":\"uint256[]\"}],\"name\":\"receiveBalanceIncrease\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverERC20FromToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC721\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"recoverERC721FromToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"guardian\",\"type\":\"address\"}],\"name\":\"removeGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"removeMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"fundingTxHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint32\",\"name\":\"fundingOutputIndex\",\"type\":\"uint32\"}],\"name\":\"requestOptimisticMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tbtcToken\",\"outputs\":[{\"internalType\":\"contract TBTC\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"unmint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"redemptionData\",\"type\":\"bytes\"}],\"name\":\"unmintAndRedeem\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpauseOptimisticMinting\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"upgradeInitiatedTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"TBTC Vault is the owner of TBTC token contract and is the only contract minting the token.\",\"kind\":\"dev\",\"methods\":{\"amountToSatoshis(uint256)\":{\"returns\":{\"convertibleAmount\":\"Amount of TBTC to be minted/unminted.\",\"remainder\":\"Not convertible remainder if amount is not divisible by SATOSHI_MULTIPLIER.\",\"satoshis\":\"Amount in satoshis - the Bank balance to be transferred for the given mint/unmint\"}},\"beginOptimisticMintingFeeUpdate(uint32)\":{\"details\":\"See the documentation for optimisticMintingFeeDivisor.\"},\"cancelOptimisticMint(bytes32,uint32)\":{\"details\":\"Guardians must validate the following conditions for every deposit for which the optimistic minting was requested: - The deposit happened on Bitcoin side and it has enough confirmations. - The optimistic minting has been requested early enough so that the wallet has enough time to sweep the deposit. - The wallet is an active one and it does perform sweeps or it will perform sweeps once the sweeps are activated.\"},\"initiateUpgrade(address)\":{\"params\":{\"_newVault\":\"The new vault address.\"}},\"mint(uint256)\":{\"details\":\"TBTC Vault must have an allowance for caller's balance in the Bank for at least `amount / SATOSHI_MULTIPLIER`.\",\"params\":{\"amount\":\"Amount of TBTC to mint.\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"receiveApproval(address,uint256,address,bytes)\":{\"details\":\"This function is doing the same as `unmint` or `unmintAndRedeem` (depending on `extraData` parameter) but it allows to execute unminting without a separate approval transaction. The function can be called only via `approveAndCall` of TBTC token.\",\"params\":{\"amount\":\"Amount of TBTC to unmint.\",\"extraData\":\"Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function. If empty, `receiveApproval` is not requesting a redemption of Bank balance but is instead performing just TBTC unminting to a Bank balance.\",\"from\":\"TBTC token holder executing unminting.\",\"token\":\"TBTC token address.\"}},\"receiveBalanceApproval(address,uint256,bytes)\":{\"details\":\"Can only be called by the Bank via `approveBalanceAndCall`.\",\"params\":{\"owner\":\"The owner who approved their Bank balance.\",\"satoshis\":\"Amount of satoshis used to mint TBTC.\"}},\"receiveBalanceIncrease(address[],uint256[])\":{\"details\":\"Fails if `depositors` array is empty. Expects the length of `depositors` and `depositedSatoshiAmounts` is the same.\"},\"recoverERC20(address,address,uint256)\":{\"params\":{\"amount\":\"Recovered amount.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC20 token contract.\"}},\"recoverERC20FromToken(address,address,uint256)\":{\"params\":{\"amount\":\"Recovered amount.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC20 token contract.\"}},\"recoverERC721(address,address,uint256,bytes)\":{\"params\":{\"data\":\"Additional data.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC721 token contract.\",\"tokenId\":\"Identifier of the recovered token.\"}},\"recoverERC721FromToken(address,address,uint256,bytes)\":{\"params\":{\"data\":\"Additional data.\",\"recipient\":\"Address the recovered token should be sent to.\",\"token\":\"Address of the recovered ERC721 token contract.\",\"tokenId\":\"Identifier of the recovered token.\"}},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"requestOptimisticMint(bytes32,uint32)\":{\"details\":\"The deposit done on the Bitcoin side must be revealed early enough to the Bridge on Ethereum to pass the Bridge's validation. The validation passes successfully only if the deposit reveal is done respectively earlier than the moment when the deposit refund locktime is reached, i.e. the deposit becomes refundable. It may happen that the wallet does not sweep a revealed deposit and one of the Minters requests an optimistic mint for that deposit just before the locktime is reached. Guardians must cancel optimistic minting for this deposit because the wallet will not be able to sweep it. The on-chain optimistic minting code does not perform any validation for gas efficiency: it would have to perform the same validation as `validateDepositRefundLocktime` and expect the entire `DepositRevealInfo` to be passed to assemble the expected script hash on-chain. Guardians must validate if the deposit happened on Bitcoin, that the script hash has the expected format, and that the wallet is an active one so they can also validate the time left for the refund.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"unmint(uint256)\":{\"details\":\"Caller must have at least `amount` of TBTC approved to TBTC Vault.\",\"params\":{\"amount\":\"Amount of TBTC to unmint.\"}},\"unmintAndRedeem(uint256,bytes)\":{\"details\":\"Caller must have at least `amount` of TBTC approved to TBTC Vault.\",\"params\":{\"amount\":\"Amount of TBTC to unmint and request to redeem in Bridge.\",\"redemptionData\":\"Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function.\"}}},\"title\":\"TBTC application vault\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"GOVERNANCE_DELAY()\":{\"notice\":\"The time delay that needs to pass between initializing and finalizing the upgrade of governable parameters.\"},\"SATOSHI_MULTIPLIER()\":{\"notice\":\"Multiplier to convert satoshi to TBTC token units.\"},\"addGuardian(address)\":{\"notice\":\"Adds the address to the Guardian set.\"},\"addMinter(address)\":{\"notice\":\"Adds the address to the Minter list.\"},\"amountToSatoshis(uint256)\":{\"notice\":\"Returns the amount of TBTC to be minted/unminted, the remainder, and the Bank balance to be transferred for the given mint/unmint. Note that if the `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account when minting or unminting.\"},\"beginOptimisticMintingDelayUpdate(uint32)\":{\"notice\":\"Begins the process of updating optimistic minting delay.\"},\"beginOptimisticMintingFeeUpdate(uint32)\":{\"notice\":\"Begins the process of updating optimistic minting fee. The fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2% of each deposit, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`.\"},\"calculateDepositKey(bytes32,uint32)\":{\"notice\":\"Calculates deposit key the same way as the Bridge contract. The deposit key is computed as `keccak256(fundingTxHash | fundingOutputIndex)`.\"},\"cancelOptimisticMint(bytes32,uint32)\":{\"notice\":\"Allows a Guardian to cancel optimistic minting request. The following conditions must be met: - The optimistic minting request for the given deposit exists. - The optimistic minting request for the given deposit has not been finalized yet. Optimistic minting request is removed. It is possible to request optimistic minting again for the same deposit later.\"},\"finalizeOptimisticMint(bytes32,uint32)\":{\"notice\":\"Allows a Minter to finalize previously requested optimistic minting. The following conditions must be met: - The optimistic minting has been requested for the given deposit. - The deposit has not been swept yet. - At least `optimisticMintingDelay` passed since the optimistic minting was requested for the given deposit. - The optimistic minting has not been finalized earlier for the given deposit. - The optimistic minting request for the given deposit has not been canceled by a Guardian. - The optimistic minting is not paused. This function mints TBTC and increases `optimisticMintingDebt` for the given depositor. The optimistic minting request is marked as finalized.\"},\"finalizeOptimisticMintingDelayUpdate()\":{\"notice\":\"Finalizes the update process of the optimistic minting delay.\"},\"finalizeOptimisticMintingFeeUpdate()\":{\"notice\":\"Finalizes the update process of the optimistic minting fee.\"},\"finalizeUpgrade()\":{\"notice\":\"Allows the governance to finalize vault upgrade process. The upgrade process needs to be first initiated with a call to `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass. Once the upgrade is finalized, the new vault becomes the owner of the TBTC token and receives the whole Bank balance of this vault.\"},\"getMinters()\":{\"notice\":\"Allows to fetch a list of all Minters.\"},\"initiateUpgrade(address)\":{\"notice\":\"Initiates vault upgrade process. The upgrade process needs to be finalized with a call to `finalizeUpgrade` function after the `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can initiate the upgrade.\"},\"isGuardian(address)\":{\"notice\":\"Indicates if the given address is a Guardian. Only Guardians can cancel requested optimistic minting.\"},\"isMinter(address)\":{\"notice\":\"Indicates if the given address is a Minter. Only Minters can request optimistic minting.\"},\"isOptimisticMintingPaused()\":{\"notice\":\"Indicates if the optimistic minting has been paused. Only the Governance can pause optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits.\"},\"mint(uint256)\":{\"notice\":\"Mints the given `amount` of TBTC to the caller previously transferring `amount / SATOSHI_MULTIPLIER` of the Bank balance from caller to TBTC Vault. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's Bank balance.\"},\"minters(uint256)\":{\"notice\":\"List of all Minters.\"},\"newOptimisticMintingDelay()\":{\"notice\":\"New optimistic minting delay value. Set only when the parameter update process is pending. Once the update gets finalized, this\"},\"newOptimisticMintingFeeDivisor()\":{\"notice\":\"New optimistic minting fee divisor value. Set only when the parameter update process is pending. Once the update gets\"},\"newVault()\":{\"notice\":\"The address of a new TBTC vault. Set only when the upgrade process is pending. Once the upgrade gets finalized, the new TBTC vault will become an owner of TBTC token.\"},\"optimisticMintingDebt(address)\":{\"notice\":\"Optimistic minting debt value per depositor's address. The debt represents the total value of all depositor's deposits revealed to the Bridge that has not been yet swept and led to the optimistic minting of TBTC. When `TBTCVault` sweeps a deposit, the debt is fully or partially paid off, no matter if that particular swept deposit was used for the optimistic minting or not. The values are in 1e18 Ethereum precision.\"},\"optimisticMintingDelay()\":{\"notice\":\"The time that needs to pass between the moment the optimistic minting is requested and the moment optimistic minting is finalized with minting TBTC.\"},\"optimisticMintingDelayUpdateInitiatedTimestamp()\":{\"notice\":\"The timestamp at which the update of the optimistic minting delay started. Zero if update is not in progress.\"},\"optimisticMintingFeeDivisor()\":{\"notice\":\"Divisor used to compute the treasury fee taken from each optimistically minted deposit and transferred to the treasury upon finalization of the optimistic mint. This fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2%, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`. The optimistic minting fee does not replace the deposit treasury fee cut by the Bridge. The optimistic fee is a percentage AFTER the treasury fee is cut: `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`\"},\"optimisticMintingFeeUpdateInitiatedTimestamp()\":{\"notice\":\"The timestamp at which the update of the optimistic minting fee divisor started. Zero if update is not in progress.\"},\"optimisticMintingRequests(uint256)\":{\"notice\":\"Collection of all revealed deposits for which the optimistic minting was requested. Indexed by a deposit key computed as `keccak256(fundingTxHash | fundingOutputIndex)`.\"},\"pauseOptimisticMinting()\":{\"notice\":\"Pauses the optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits.\"},\"receiveApproval(address,uint256,address,bytes)\":{\"notice\":\"Burns `amount` of TBTC from the caller's balance. If `extraData` is empty, transfers `amount` back to the caller's balance in the Bank. If `extraData` is not empty, requests redemption in the Bridge using the `extraData` as a `redemptionData` parameter to Bridge's `receiveBalanceApproval` function. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account. Note that it may left a token approval equal to the remainder.\"},\"receiveBalanceApproval(address,uint256,bytes)\":{\"notice\":\"Transfers `satoshis` of the Bank balance from the caller to TBTC Vault and mints `satoshis * SATOSHI_MULTIPLIER` of TBTC to the caller.\"},\"receiveBalanceIncrease(address[],uint256[])\":{\"notice\":\"Mints the same amount of TBTC as the deposited satoshis amount multiplied by SATOSHI_MULTIPLIER for each depositor in the array. Can only be called by the Bank after the Bridge swept deposits and Bank increased balance for the vault.\"},\"recoverERC20(address,address,uint256)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC20 token sent - mistakenly or not - to the vault address. This function should be used to withdraw TBTC v1 tokens transferred to TBTCVault as a result of VendingMachine > TBTCVault upgrade.\"},\"recoverERC20FromToken(address,address,uint256)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC20 token sent mistakenly to the TBTC token contract address.\"},\"recoverERC721(address,address,uint256,bytes)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the vault address.\"},\"recoverERC721FromToken(address,address,uint256,bytes)\":{\"notice\":\"Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the TBTC token contract address.\"},\"removeGuardian(address)\":{\"notice\":\"Removes the address from the Guardian set.\"},\"removeMinter(address)\":{\"notice\":\"Removes the address from the Minter list.\"},\"requestOptimisticMint(bytes32,uint32)\":{\"notice\":\"Allows a Minter to request for an optimistic minting of TBTC. The following conditions must be met: - There is no optimistic minting request for the deposit, finalized or not. - The deposit with the given Bitcoin funding transaction hash and output index has been revealed to the Bridge. - The deposit has not been swept yet. - The deposit is targeted into the TBTCVault. - The optimistic minting is not paused. After calling this function, the Minter has to wait for `optimisticMintingDelay` before finalizing the mint with a call to finalizeOptimisticMint.\"},\"unmint(uint256)\":{\"notice\":\"Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` back to the caller's balance in the Bank. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account.\"},\"unmintAndRedeem(uint256,bytes)\":{\"notice\":\"Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` of Bank balance to the Bridge requesting redemption based on the provided `redemptionData`. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account.\"},\"unpauseOptimisticMinting()\":{\"notice\":\"Unpauses the optimistic minting.\"},\"upgradeInitiatedTimestamp()\":{\"notice\":\"The timestamp at which an upgrade to a new TBTC vault was initiated. Set only when the upgrade process is pending.\"}},\"notice\":\"TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of Bitcoin. It facilitates Bitcoin holders to act on the Ethereum blockchain and access the decentralized finance (DeFi) ecosystem. TBTC Vault mints and unmints TBTC based on Bitcoin balances in the Bank.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/vault/TBTCVault.sol\":\"TBTCVault\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":1000},\"remappings\":[]},\"sources\":{\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/** @title BitcoinSPV */\\n/** @author Summa (https://summa.one) */\\n\\nimport {BytesLib} from \\\"./BytesLib.sol\\\";\\nimport {SafeMath} from \\\"./SafeMath.sol\\\";\\n\\nlibrary BTCUtils {\\n using BytesLib for bytes;\\n using SafeMath for uint256;\\n\\n // The target at minimum Difficulty. Also the target of the genesis block\\n uint256 public constant DIFF1_TARGET = 0xffff0000000000000000000000000000000000000000000000000000;\\n\\n uint256 public constant RETARGET_PERIOD = 2 * 7 * 24 * 60 * 60; // 2 weeks in seconds\\n uint256 public constant RETARGET_PERIOD_BLOCKS = 2016; // 2 weeks in blocks\\n\\n uint256 public constant ERR_BAD_ARG = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\\n\\n /* ***** */\\n /* UTILS */\\n /* ***** */\\n\\n /// @notice Determines the length of a VarInt in bytes\\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\\n /// @param _flag The first byte of a VarInt\\n /// @return The number of non-flag bytes in the VarInt\\n function determineVarIntDataLength(bytes memory _flag) internal pure returns (uint8) {\\n return determineVarIntDataLengthAt(_flag, 0);\\n }\\n\\n /// @notice Determines the length of a VarInt in bytes\\n /// @dev A VarInt of >1 byte is prefixed with a flag indicating its length\\n /// @param _b The byte array containing a VarInt\\n /// @param _at The position of the VarInt in the array\\n /// @return The number of non-flag bytes in the VarInt\\n function determineVarIntDataLengthAt(bytes memory _b, uint256 _at) internal pure returns (uint8) {\\n if (uint8(_b[_at]) == 0xff) {\\n return 8; // one-byte flag, 8 bytes data\\n }\\n if (uint8(_b[_at]) == 0xfe) {\\n return 4; // one-byte flag, 4 bytes data\\n }\\n if (uint8(_b[_at]) == 0xfd) {\\n return 2; // one-byte flag, 2 bytes data\\n }\\n\\n return 0; // flag is data\\n }\\n\\n /// @notice Parse a VarInt into its data length and the number it represents\\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\\n /// Caller SHOULD explicitly handle this case (or bubble it up)\\n /// @param _b A byte-string starting with a VarInt\\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\\n function parseVarInt(bytes memory _b) internal pure returns (uint256, uint256) {\\n return parseVarIntAt(_b, 0);\\n }\\n\\n /// @notice Parse a VarInt into its data length and the number it represents\\n /// @dev Useful for Parsing Vins and Vouts. Returns ERR_BAD_ARG if insufficient bytes.\\n /// Caller SHOULD explicitly handle this case (or bubble it up)\\n /// @param _b A byte-string containing a VarInt\\n /// @param _at The position of the VarInt\\n /// @return number of bytes in the encoding (not counting the tag), the encoded int\\n function parseVarIntAt(bytes memory _b, uint256 _at) internal pure returns (uint256, uint256) {\\n uint8 _dataLen = determineVarIntDataLengthAt(_b, _at);\\n\\n if (_dataLen == 0) {\\n return (0, uint8(_b[_at]));\\n }\\n if (_b.length < 1 + _dataLen + _at) {\\n return (ERR_BAD_ARG, 0);\\n }\\n uint256 _number;\\n if (_dataLen == 2) {\\n _number = reverseUint16(uint16(_b.slice2(1 + _at)));\\n } else if (_dataLen == 4) {\\n _number = reverseUint32(uint32(_b.slice4(1 + _at)));\\n } else if (_dataLen == 8) {\\n _number = reverseUint64(uint64(_b.slice8(1 + _at)));\\n }\\n return (_dataLen, _number);\\n }\\n\\n /// @notice Changes the endianness of a byte array\\n /// @dev Returns a new, backwards, bytes\\n /// @param _b The bytes to reverse\\n /// @return The reversed bytes\\n function reverseEndianness(bytes memory _b) internal pure returns (bytes memory) {\\n bytes memory _newValue = new bytes(_b.length);\\n\\n for (uint i = 0; i < _b.length; i++) {\\n _newValue[_b.length - i - 1] = _b[i];\\n }\\n\\n return _newValue;\\n }\\n\\n /// @notice Changes the endianness of a uint256\\n /// @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint256(uint256 _b) internal pure returns (uint256 v) {\\n v = _b;\\n\\n // swap bytes\\n v = ((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |\\n ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);\\n // swap 2-byte long pairs\\n v = ((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |\\n ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);\\n // swap 4-byte long pairs\\n v = ((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |\\n ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);\\n // swap 8-byte long pairs\\n v = ((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |\\n ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);\\n // swap 16-byte long pairs\\n v = (v >> 128) | (v << 128);\\n }\\n\\n /// @notice Changes the endianness of a uint64\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint64(uint64 _b) internal pure returns (uint64 v) {\\n v = _b;\\n\\n // swap bytes\\n v = ((v >> 8) & 0x00FF00FF00FF00FF) |\\n ((v & 0x00FF00FF00FF00FF) << 8);\\n // swap 2-byte long pairs\\n v = ((v >> 16) & 0x0000FFFF0000FFFF) |\\n ((v & 0x0000FFFF0000FFFF) << 16);\\n // swap 4-byte long pairs\\n v = (v >> 32) | (v << 32);\\n }\\n\\n /// @notice Changes the endianness of a uint32\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint32(uint32 _b) internal pure returns (uint32 v) {\\n v = _b;\\n\\n // swap bytes\\n v = ((v >> 8) & 0x00FF00FF) |\\n ((v & 0x00FF00FF) << 8);\\n // swap 2-byte long pairs\\n v = (v >> 16) | (v << 16);\\n }\\n\\n /// @notice Changes the endianness of a uint24\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint24(uint24 _b) internal pure returns (uint24 v) {\\n v = (_b << 16) | (_b & 0x00FF00) | (_b >> 16);\\n }\\n\\n /// @notice Changes the endianness of a uint16\\n /// @param _b The unsigned integer to reverse\\n /// @return v The reversed value\\n function reverseUint16(uint16 _b) internal pure returns (uint16 v) {\\n v = (_b << 8) | (_b >> 8);\\n }\\n\\n\\n /// @notice Converts big-endian bytes to a uint\\n /// @dev Traverses the byte array and sums the bytes\\n /// @param _b The big-endian bytes-encoded integer\\n /// @return The integer representation\\n function bytesToUint(bytes memory _b) internal pure returns (uint256) {\\n uint256 _number;\\n\\n for (uint i = 0; i < _b.length; i++) {\\n _number = _number + uint8(_b[i]) * (2 ** (8 * (_b.length - (i + 1))));\\n }\\n\\n return _number;\\n }\\n\\n /// @notice Get the last _num bytes from a byte array\\n /// @param _b The byte array to slice\\n /// @param _num The number of bytes to extract from the end\\n /// @return The last _num bytes of _b\\n function lastBytes(bytes memory _b, uint256 _num) internal pure returns (bytes memory) {\\n uint256 _start = _b.length.sub(_num);\\n\\n return _b.slice(_start, _num);\\n }\\n\\n /// @notice Implements bitcoin's hash160 (rmd160(sha2()))\\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\\n /// @param _b The pre-image\\n /// @return The digest\\n function hash160(bytes memory _b) internal pure returns (bytes memory) {\\n return abi.encodePacked(ripemd160(abi.encodePacked(sha256(_b))));\\n }\\n\\n /// @notice Implements bitcoin's hash160 (sha2 + ripemd160)\\n /// @dev sha2 precompile at address(2), ripemd160 at address(3)\\n /// @param _b The pre-image\\n /// @return res The digest\\n function hash160View(bytes memory _b) internal view returns (bytes20 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\\n pop(staticcall(gas(), 3, 0x00, 32, 0x00, 32))\\n // read from position 12 = 0c\\n res := mload(0x0c)\\n }\\n }\\n\\n /// @notice Implements bitcoin's hash256 (double sha2)\\n /// @dev abi.encodePacked changes the return to bytes instead of bytes32\\n /// @param _b The pre-image\\n /// @return The digest\\n function hash256(bytes memory _b) internal pure returns (bytes32) {\\n return sha256(abi.encodePacked(sha256(_b)));\\n }\\n\\n /// @notice Implements bitcoin's hash256 (double sha2)\\n /// @dev sha2 is precompiled smart contract located at address(2)\\n /// @param _b The pre-image\\n /// @return res The digest\\n function hash256View(bytes memory _b) internal view returns (bytes32 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n pop(staticcall(gas(), 2, add(_b, 32), mload(_b), 0x00, 32))\\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\\n res := mload(0x00)\\n }\\n }\\n\\n /// @notice Implements bitcoin's hash256 on a pair of bytes32\\n /// @dev sha2 is precompiled smart contract located at address(2)\\n /// @param _a The first bytes32 of the pre-image\\n /// @param _b The second bytes32 of the pre-image\\n /// @return res The digest\\n function hash256Pair(bytes32 _a, bytes32 _b) internal view returns (bytes32 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n mstore(0x00, _a)\\n mstore(0x20, _b)\\n pop(staticcall(gas(), 2, 0x00, 64, 0x00, 32))\\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\\n res := mload(0x00)\\n }\\n }\\n\\n /// @notice Implements bitcoin's hash256 (double sha2)\\n /// @dev sha2 is precompiled smart contract located at address(2)\\n /// @param _b The array containing the pre-image\\n /// @param at The start of the pre-image\\n /// @param len The length of the pre-image\\n /// @return res The digest\\n function hash256Slice(\\n bytes memory _b,\\n uint256 at,\\n uint256 len\\n ) internal view returns (bytes32 res) {\\n // solium-disable-next-line security/no-inline-assembly\\n assembly {\\n pop(staticcall(gas(), 2, add(_b, add(32, at)), len, 0x00, 32))\\n pop(staticcall(gas(), 2, 0x00, 32, 0x00, 32))\\n res := mload(0x00)\\n }\\n }\\n\\n /* ************ */\\n /* Legacy Input */\\n /* ************ */\\n\\n /// @notice Extracts the nth input from the vin (0-indexed)\\n /// @dev Iterates over the vin. If you need to extract several, write a custom function\\n /// @param _vin The vin as a tightly-packed byte array\\n /// @param _index The 0-indexed location of the input to extract\\n /// @return The input as a byte array\\n function extractInputAtIndex(bytes memory _vin, uint256 _index) internal pure returns (bytes memory) {\\n uint256 _varIntDataLen;\\n uint256 _nIns;\\n\\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Read overrun during VarInt parsing\\\");\\n require(_index < _nIns, \\\"Vin read overrun\\\");\\n\\n uint256 _len = 0;\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 _i = 0; _i < _index; _i ++) {\\n _len = determineInputLengthAt(_vin, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n _offset = _offset + _len;\\n }\\n\\n _len = determineInputLengthAt(_vin, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n return _vin.slice(_offset, _len);\\n }\\n\\n /// @notice Determines whether an input is legacy\\n /// @dev False if no scriptSig, otherwise True\\n /// @param _input The input\\n /// @return True for legacy, False for witness\\n function isLegacyInput(bytes memory _input) internal pure returns (bool) {\\n return _input[36] != hex\\\"00\\\";\\n }\\n\\n /// @notice Determines the length of a scriptSig in an input\\n /// @dev Will return 0 if passed a witness input.\\n /// @param _input The LEGACY input\\n /// @return The length of the script sig\\n function extractScriptSigLen(bytes memory _input) internal pure returns (uint256, uint256) {\\n return extractScriptSigLenAt(_input, 0);\\n }\\n\\n /// @notice Determines the length of a scriptSig in an input\\n /// starting at the specified position\\n /// @dev Will return 0 if passed a witness input.\\n /// @param _input The byte array containing the LEGACY input\\n /// @param _at The position of the input in the array\\n /// @return The length of the script sig\\n function extractScriptSigLenAt(bytes memory _input, uint256 _at) internal pure returns (uint256, uint256) {\\n if (_input.length < 37 + _at) {\\n return (ERR_BAD_ARG, 0);\\n }\\n\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = parseVarIntAt(_input, _at + 36);\\n\\n return (_varIntDataLen, _scriptSigLen);\\n }\\n\\n /// @notice Determines the length of an input from its scriptSig\\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\\n /// @param _input The input\\n /// @return The length of the input in bytes\\n function determineInputLength(bytes memory _input) internal pure returns (uint256) {\\n return determineInputLengthAt(_input, 0);\\n }\\n\\n /// @notice Determines the length of an input from its scriptSig,\\n /// starting at the specified position\\n /// @dev 36 for outpoint, 1 for scriptSig length, 4 for sequence\\n /// @param _input The byte array containing the input\\n /// @param _at The position of the input in the array\\n /// @return The length of the input in bytes\\n function determineInputLengthAt(bytes memory _input, uint256 _at) internal pure returns (uint256) {\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLenAt(_input, _at);\\n if (_varIntDataLen == ERR_BAD_ARG) {\\n return ERR_BAD_ARG;\\n }\\n\\n return 36 + 1 + _varIntDataLen + _scriptSigLen + 4;\\n }\\n\\n /// @notice Extracts the LE sequence bytes from an input\\n /// @dev Sequence is used for relative time locks\\n /// @param _input The LEGACY input\\n /// @return The sequence bytes (LE uint)\\n function extractSequenceLELegacy(bytes memory _input) internal pure returns (bytes4) {\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n return _input.slice4(36 + 1 + _varIntDataLen + _scriptSigLen);\\n }\\n\\n /// @notice Extracts the sequence from the input\\n /// @dev Sequence is a 4-byte little-endian number\\n /// @param _input The LEGACY input\\n /// @return The sequence number (big-endian uint)\\n function extractSequenceLegacy(bytes memory _input) internal pure returns (uint32) {\\n uint32 _leSeqence = uint32(extractSequenceLELegacy(_input));\\n uint32 _beSequence = reverseUint32(_leSeqence);\\n return _beSequence;\\n }\\n /// @notice Extracts the VarInt-prepended scriptSig from the input in a tx\\n /// @dev Will return hex\\\"00\\\" if passed a witness input\\n /// @param _input The LEGACY input\\n /// @return The length-prepended scriptSig\\n function extractScriptSig(bytes memory _input) internal pure returns (bytes memory) {\\n uint256 _varIntDataLen;\\n uint256 _scriptSigLen;\\n (_varIntDataLen, _scriptSigLen) = extractScriptSigLen(_input);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Bad VarInt in scriptSig\\\");\\n return _input.slice(36, 1 + _varIntDataLen + _scriptSigLen);\\n }\\n\\n\\n /* ************* */\\n /* Witness Input */\\n /* ************* */\\n\\n /// @notice Extracts the LE sequence bytes from an input\\n /// @dev Sequence is used for relative time locks\\n /// @param _input The WITNESS input\\n /// @return The sequence bytes (LE uint)\\n function extractSequenceLEWitness(bytes memory _input) internal pure returns (bytes4) {\\n return _input.slice4(37);\\n }\\n\\n /// @notice Extracts the sequence from the input in a tx\\n /// @dev Sequence is a 4-byte little-endian number\\n /// @param _input The WITNESS input\\n /// @return The sequence number (big-endian uint)\\n function extractSequenceWitness(bytes memory _input) internal pure returns (uint32) {\\n uint32 _leSeqence = uint32(extractSequenceLEWitness(_input));\\n uint32 _inputeSequence = reverseUint32(_leSeqence);\\n return _inputeSequence;\\n }\\n\\n /// @notice Extracts the outpoint from the input in a tx\\n /// @dev 32-byte tx id with 4-byte index\\n /// @param _input The input\\n /// @return The outpoint (LE bytes of prev tx hash + LE bytes of prev tx index)\\n function extractOutpoint(bytes memory _input) internal pure returns (bytes memory) {\\n return _input.slice(0, 36);\\n }\\n\\n /// @notice Extracts the outpoint tx id from an input\\n /// @dev 32-byte tx id\\n /// @param _input The input\\n /// @return The tx id (little-endian bytes)\\n function extractInputTxIdLE(bytes memory _input) internal pure returns (bytes32) {\\n return _input.slice32(0);\\n }\\n\\n /// @notice Extracts the outpoint tx id from an input\\n /// starting at the specified position\\n /// @dev 32-byte tx id\\n /// @param _input The byte array containing the input\\n /// @param _at The position of the input\\n /// @return The tx id (little-endian bytes)\\n function extractInputTxIdLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes32) {\\n return _input.slice32(_at);\\n }\\n\\n /// @notice Extracts the LE tx input index from the input in a tx\\n /// @dev 4-byte tx index\\n /// @param _input The input\\n /// @return The tx index (little-endian bytes)\\n function extractTxIndexLE(bytes memory _input) internal pure returns (bytes4) {\\n return _input.slice4(32);\\n }\\n\\n /// @notice Extracts the LE tx input index from the input in a tx\\n /// starting at the specified position\\n /// @dev 4-byte tx index\\n /// @param _input The byte array containing the input\\n /// @param _at The position of the input\\n /// @return The tx index (little-endian bytes)\\n function extractTxIndexLeAt(bytes memory _input, uint256 _at) internal pure returns (bytes4) {\\n return _input.slice4(32 + _at);\\n }\\n\\n /* ****** */\\n /* Output */\\n /* ****** */\\n\\n /// @notice Determines the length of an output\\n /// @dev Works with any properly formatted output\\n /// @param _output The output\\n /// @return The length indicated by the prefix, error if invalid length\\n function determineOutputLength(bytes memory _output) internal pure returns (uint256) {\\n return determineOutputLengthAt(_output, 0);\\n }\\n\\n /// @notice Determines the length of an output\\n /// starting at the specified position\\n /// @dev Works with any properly formatted output\\n /// @param _output The byte array containing the output\\n /// @param _at The position of the output\\n /// @return The length indicated by the prefix, error if invalid length\\n function determineOutputLengthAt(bytes memory _output, uint256 _at) internal pure returns (uint256) {\\n if (_output.length < 9 + _at) {\\n return ERR_BAD_ARG;\\n }\\n uint256 _varIntDataLen;\\n uint256 _scriptPubkeyLength;\\n (_varIntDataLen, _scriptPubkeyLength) = parseVarIntAt(_output, 8 + _at);\\n\\n if (_varIntDataLen == ERR_BAD_ARG) {\\n return ERR_BAD_ARG;\\n }\\n\\n // 8-byte value, 1-byte for tag itself\\n return 8 + 1 + _varIntDataLen + _scriptPubkeyLength;\\n }\\n\\n /// @notice Extracts the output at a given index in the TxOuts vector\\n /// @dev Iterates over the vout. If you need to extract multiple, write a custom function\\n /// @param _vout The _vout to extract from\\n /// @param _index The 0-indexed location of the output to extract\\n /// @return The specified output\\n function extractOutputAtIndex(bytes memory _vout, uint256 _index) internal pure returns (bytes memory) {\\n uint256 _varIntDataLen;\\n uint256 _nOuts;\\n\\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\\n require(_varIntDataLen != ERR_BAD_ARG, \\\"Read overrun during VarInt parsing\\\");\\n require(_index < _nOuts, \\\"Vout read overrun\\\");\\n\\n uint256 _len = 0;\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 _i = 0; _i < _index; _i ++) {\\n _len = determineOutputLengthAt(_vout, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptPubkey\\\");\\n _offset += _len;\\n }\\n\\n _len = determineOutputLengthAt(_vout, _offset);\\n require(_len != ERR_BAD_ARG, \\\"Bad VarInt in scriptPubkey\\\");\\n return _vout.slice(_offset, _len);\\n }\\n\\n /// @notice Extracts the value bytes from the output in a tx\\n /// @dev Value is an 8-byte little-endian number\\n /// @param _output The output\\n /// @return The output value as LE bytes\\n function extractValueLE(bytes memory _output) internal pure returns (bytes8) {\\n return _output.slice8(0);\\n }\\n\\n /// @notice Extracts the value from the output in a tx\\n /// @dev Value is an 8-byte little-endian number\\n /// @param _output The output\\n /// @return The output value\\n function extractValue(bytes memory _output) internal pure returns (uint64) {\\n uint64 _leValue = uint64(extractValueLE(_output));\\n uint64 _beValue = reverseUint64(_leValue);\\n return _beValue;\\n }\\n\\n /// @notice Extracts the value from the output in a tx\\n /// @dev Value is an 8-byte little-endian number\\n /// @param _output The byte array containing the output\\n /// @param _at The starting index of the output in the array\\n /// @return The output value\\n function extractValueAt(bytes memory _output, uint256 _at) internal pure returns (uint64) {\\n uint64 _leValue = uint64(_output.slice8(_at));\\n uint64 _beValue = reverseUint64(_leValue);\\n return _beValue;\\n }\\n\\n /// @notice Extracts the data from an op return output\\n /// @dev Returns hex\\\"\\\" if no data or not an op return\\n /// @param _output The output\\n /// @return Any data contained in the opreturn output, null if not an op return\\n function extractOpReturnData(bytes memory _output) internal pure returns (bytes memory) {\\n if (_output[9] != hex\\\"6a\\\") {\\n return hex\\\"\\\";\\n }\\n bytes1 _dataLen = _output[10];\\n return _output.slice(11, uint256(uint8(_dataLen)));\\n }\\n\\n /// @notice Extracts the hash from the output script\\n /// @dev Determines type by the length prefix and validates format\\n /// @param _output The output\\n /// @return The hash committed to by the pk_script, or null for errors\\n function extractHash(bytes memory _output) internal pure returns (bytes memory) {\\n return extractHashAt(_output, 8, _output.length - 8);\\n }\\n\\n /// @notice Extracts the hash from the output script\\n /// @dev Determines type by the length prefix and validates format\\n /// @param _output The byte array containing the output\\n /// @param _at The starting index of the output script in the array\\n /// (output start + 8)\\n /// @param _len The length of the output script\\n /// (output length - 8)\\n /// @return The hash committed to by the pk_script, or null for errors\\n function extractHashAt(\\n bytes memory _output,\\n uint256 _at,\\n uint256 _len\\n ) internal pure returns (bytes memory) {\\n uint8 _scriptLen = uint8(_output[_at]);\\n\\n // don't have to worry about overflow here.\\n // if _scriptLen + 1 overflows, then output length would have to be < 1\\n // for this check to pass. if it's < 1, then we errored when assigning\\n // _scriptLen\\n if (_scriptLen + 1 != _len) {\\n return hex\\\"\\\";\\n }\\n\\n if (uint8(_output[_at + 1]) == 0) {\\n if (_scriptLen < 2) {\\n return hex\\\"\\\";\\n }\\n uint256 _payloadLen = uint8(_output[_at + 2]);\\n // Check for maliciously formatted witness outputs.\\n // No need to worry about underflow as long b/c of the `< 2` check\\n if (_payloadLen != _scriptLen - 2 || (_payloadLen != 0x20 && _payloadLen != 0x14)) {\\n return hex\\\"\\\";\\n }\\n return _output.slice(_at + 3, _payloadLen);\\n } else {\\n bytes3 _tag = _output.slice3(_at);\\n // p2pkh\\n if (_tag == hex\\\"1976a9\\\") {\\n // Check for maliciously formatted p2pkh\\n // No need to worry about underflow, b/c of _scriptLen check\\n if (uint8(_output[_at + 3]) != 0x14 ||\\n _output.slice2(_at + _len - 2) != hex\\\"88ac\\\") {\\n return hex\\\"\\\";\\n }\\n return _output.slice(_at + 4, 20);\\n //p2sh\\n } else if (_tag == hex\\\"17a914\\\") {\\n // Check for maliciously formatted p2sh\\n // No need to worry about underflow, b/c of _scriptLen check\\n if (uint8(_output[_at + _len - 1]) != 0x87) {\\n return hex\\\"\\\";\\n }\\n return _output.slice(_at + 3, 20);\\n }\\n }\\n return hex\\\"\\\"; /* NB: will trigger on OPRETURN and any non-standard that doesn't overrun */\\n }\\n\\n /* ********** */\\n /* Witness TX */\\n /* ********** */\\n\\n\\n /// @notice Checks that the vin passed up is properly formatted\\n /// @dev Consider a vin with a valid vout in its scriptsig\\n /// @param _vin Raw bytes length-prefixed input vector\\n /// @return True if it represents a validly formatted vin\\n function validateVin(bytes memory _vin) internal pure returns (bool) {\\n uint256 _varIntDataLen;\\n uint256 _nIns;\\n\\n (_varIntDataLen, _nIns) = parseVarInt(_vin);\\n\\n // Not valid if it says there are too many or no inputs\\n if (_nIns == 0 || _varIntDataLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 i = 0; i < _nIns; i++) {\\n // If we're at the end, but still expect more\\n if (_offset >= _vin.length) {\\n return false;\\n }\\n\\n // Grab the next input and determine its length.\\n uint256 _nextLen = determineInputLengthAt(_vin, _offset);\\n if (_nextLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n // Increase the offset by that much\\n _offset += _nextLen;\\n }\\n\\n // Returns false if we're not exactly at the end\\n return _offset == _vin.length;\\n }\\n\\n /// @notice Checks that the vout passed up is properly formatted\\n /// @dev Consider a vout with a valid scriptpubkey\\n /// @param _vout Raw bytes length-prefixed output vector\\n /// @return True if it represents a validly formatted vout\\n function validateVout(bytes memory _vout) internal pure returns (bool) {\\n uint256 _varIntDataLen;\\n uint256 _nOuts;\\n\\n (_varIntDataLen, _nOuts) = parseVarInt(_vout);\\n\\n // Not valid if it says there are too many or no outputs\\n if (_nOuts == 0 || _varIntDataLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n uint256 _offset = 1 + _varIntDataLen;\\n\\n for (uint256 i = 0; i < _nOuts; i++) {\\n // If we're at the end, but still expect more\\n if (_offset >= _vout.length) {\\n return false;\\n }\\n\\n // Grab the next output and determine its length.\\n // Increase the offset by that much\\n uint256 _nextLen = determineOutputLengthAt(_vout, _offset);\\n if (_nextLen == ERR_BAD_ARG) {\\n return false;\\n }\\n\\n _offset += _nextLen;\\n }\\n\\n // Returns false if we're not exactly at the end\\n return _offset == _vout.length;\\n }\\n\\n\\n\\n /* ************ */\\n /* Block Header */\\n /* ************ */\\n\\n /// @notice Extracts the transaction merkle root from a block header\\n /// @dev Use verifyHash256Merkle to verify proofs with this root\\n /// @param _header The header\\n /// @return The merkle root (little-endian)\\n function extractMerkleRootLE(bytes memory _header) internal pure returns (bytes32) {\\n return _header.slice32(36);\\n }\\n\\n /// @notice Extracts the target from a block header\\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\\n /// @param _header The header\\n /// @return The target threshold\\n function extractTarget(bytes memory _header) internal pure returns (uint256) {\\n return extractTargetAt(_header, 0);\\n }\\n\\n /// @notice Extracts the target from a block header\\n /// @dev Target is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\\n /// @param _header The array containing the header\\n /// @param at The start of the header\\n /// @return The target threshold\\n function extractTargetAt(bytes memory _header, uint256 at) internal pure returns (uint256) {\\n uint24 _m = uint24(_header.slice3(72 + at));\\n uint8 _e = uint8(_header[75 + at]);\\n uint256 _mantissa = uint256(reverseUint24(_m));\\n uint _exponent = _e - 3;\\n\\n return _mantissa * (256 ** _exponent);\\n }\\n\\n /// @notice Calculate difficulty from the difficulty 1 target and current target\\n /// @dev Difficulty 1 is 0x1d00ffff on mainnet and testnet\\n /// @dev Difficulty 1 is a 256-bit number encoded as a 3-byte mantissa and 1-byte exponent\\n /// @param _target The current target\\n /// @return The block difficulty (bdiff)\\n function calculateDifficulty(uint256 _target) internal pure returns (uint256) {\\n // Difficulty 1 calculated from 0x1d00ffff\\n return DIFF1_TARGET.div(_target);\\n }\\n\\n /// @notice Extracts the previous block's hash from a block header\\n /// @dev Block headers do NOT include block number :(\\n /// @param _header The header\\n /// @return The previous block's hash (little-endian)\\n function extractPrevBlockLE(bytes memory _header) internal pure returns (bytes32) {\\n return _header.slice32(4);\\n }\\n\\n /// @notice Extracts the previous block's hash from a block header\\n /// @dev Block headers do NOT include block number :(\\n /// @param _header The array containing the header\\n /// @param at The start of the header\\n /// @return The previous block's hash (little-endian)\\n function extractPrevBlockLEAt(\\n bytes memory _header,\\n uint256 at\\n ) internal pure returns (bytes32) {\\n return _header.slice32(4 + at);\\n }\\n\\n /// @notice Extracts the timestamp from a block header\\n /// @dev Time is not 100% reliable\\n /// @param _header The header\\n /// @return The timestamp (little-endian bytes)\\n function extractTimestampLE(bytes memory _header) internal pure returns (bytes4) {\\n return _header.slice4(68);\\n }\\n\\n /// @notice Extracts the timestamp from a block header\\n /// @dev Time is not 100% reliable\\n /// @param _header The header\\n /// @return The timestamp (uint)\\n function extractTimestamp(bytes memory _header) internal pure returns (uint32) {\\n return reverseUint32(uint32(extractTimestampLE(_header)));\\n }\\n\\n /// @notice Extracts the expected difficulty from a block header\\n /// @dev Does NOT verify the work\\n /// @param _header The header\\n /// @return The difficulty as an integer\\n function extractDifficulty(bytes memory _header) internal pure returns (uint256) {\\n return calculateDifficulty(extractTarget(_header));\\n }\\n\\n /// @notice Concatenates and hashes two inputs for merkle proving\\n /// @param _a The first hash\\n /// @param _b The second hash\\n /// @return The double-sha256 of the concatenated hashes\\n function _hash256MerkleStep(bytes memory _a, bytes memory _b) internal view returns (bytes32) {\\n return hash256View(abi.encodePacked(_a, _b));\\n }\\n\\n /// @notice Concatenates and hashes two inputs for merkle proving\\n /// @param _a The first hash\\n /// @param _b The second hash\\n /// @return The double-sha256 of the concatenated hashes\\n function _hash256MerkleStep(bytes32 _a, bytes32 _b) internal view returns (bytes32) {\\n return hash256Pair(_a, _b);\\n }\\n\\n\\n /// @notice Verifies a Bitcoin-style merkle tree\\n /// @dev Leaves are 0-indexed. Inefficient version.\\n /// @param _proof The proof. Tightly packed LE sha256 hashes. The last hash is the root\\n /// @param _index The index of the leaf\\n /// @return true if the proof is valid, else false\\n function verifyHash256Merkle(bytes memory _proof, uint _index) internal view returns (bool) {\\n // Not an even number of hashes\\n if (_proof.length % 32 != 0) {\\n return false;\\n }\\n\\n // Special case for coinbase-only blocks\\n if (_proof.length == 32) {\\n return true;\\n }\\n\\n // Should never occur\\n if (_proof.length == 64) {\\n return false;\\n }\\n\\n bytes32 _root = _proof.slice32(_proof.length - 32);\\n bytes32 _current = _proof.slice32(0);\\n bytes memory _tree = _proof.slice(32, _proof.length - 64);\\n\\n return verifyHash256Merkle(_current, _tree, _root, _index);\\n }\\n\\n /// @notice Verifies a Bitcoin-style merkle tree\\n /// @dev Leaves are 0-indexed. Efficient version.\\n /// @param _leaf The leaf of the proof. LE sha256 hash.\\n /// @param _tree The intermediate nodes in the proof.\\n /// Tightly packed LE sha256 hashes.\\n /// @param _root The root of the proof. LE sha256 hash.\\n /// @param _index The index of the leaf\\n /// @return true if the proof is valid, else false\\n function verifyHash256Merkle(\\n bytes32 _leaf,\\n bytes memory _tree,\\n bytes32 _root,\\n uint _index\\n ) internal view returns (bool) {\\n // Not an even number of hashes\\n if (_tree.length % 32 != 0) {\\n return false;\\n }\\n\\n // Should never occur\\n if (_tree.length == 0) {\\n return false;\\n }\\n\\n uint _idx = _index;\\n bytes32 _current = _leaf;\\n\\n // i moves in increments of 32\\n for (uint i = 0; i < _tree.length; i += 32) {\\n if (_idx % 2 == 1) {\\n _current = _hash256MerkleStep(_tree.slice32(i), _current);\\n } else {\\n _current = _hash256MerkleStep(_current, _tree.slice32(i));\\n }\\n _idx = _idx >> 1;\\n }\\n return _current == _root;\\n }\\n\\n /*\\n NB: https://github.com/bitcoin/bitcoin/blob/78dae8caccd82cfbfd76557f1fb7d7557c7b5edb/src/pow.cpp#L49-L72\\n NB: We get a full-bitlength target from this. For comparison with\\n header-encoded targets we need to mask it with the header target\\n e.g. (full & truncated) == truncated\\n */\\n /// @notice performs the bitcoin difficulty retarget\\n /// @dev implements the Bitcoin algorithm precisely\\n /// @param _previousTarget the target of the previous period\\n /// @param _firstTimestamp the timestamp of the first block in the difficulty period\\n /// @param _secondTimestamp the timestamp of the last block in the difficulty period\\n /// @return the new period's target threshold\\n function retargetAlgorithm(\\n uint256 _previousTarget,\\n uint256 _firstTimestamp,\\n uint256 _secondTimestamp\\n ) internal pure returns (uint256) {\\n uint256 _elapsedTime = _secondTimestamp.sub(_firstTimestamp);\\n\\n // Normalize ratio to factor of 4 if very long or very short\\n if (_elapsedTime < RETARGET_PERIOD.div(4)) {\\n _elapsedTime = RETARGET_PERIOD.div(4);\\n }\\n if (_elapsedTime > RETARGET_PERIOD.mul(4)) {\\n _elapsedTime = RETARGET_PERIOD.mul(4);\\n }\\n\\n /*\\n NB: high targets e.g. ffff0020 can cause overflows here\\n so we divide it by 256**2, then multiply by 256**2 later\\n we know the target is evenly divisible by 256**2, so this isn't an issue\\n */\\n\\n uint256 _adjusted = _previousTarget.div(65536).mul(_elapsedTime);\\n return _adjusted.div(RETARGET_PERIOD).mul(65536);\\n }\\n}\\n\",\"keccak256\":\"0x439eaa97e9239705f3d31e8d39dccbad32311f1f119e295d53c65e0ae3c5a5fc\"},\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/*\\n\\nhttps://github.com/GNSPS/solidity-bytes-utils/\\n\\nThis is free and unencumbered software released into the public domain.\\n\\nAnyone is free to copy, modify, publish, use, compile, sell, or\\ndistribute this software, either in source code form or as a compiled\\nbinary, for any purpose, commercial or non-commercial, and by any\\nmeans.\\n\\nIn jurisdictions that recognize copyright laws, the author or authors\\nof this software dedicate any and all copyright interest in the\\nsoftware to the public domain. We make this dedication for the benefit\\nof the public at large and to the detriment of our heirs and\\nsuccessors. We intend this dedication to be an overt act of\\nrelinquishment in perpetuity of all present and future rights to this\\nsoftware under copyright law.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND,\\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\\nOTHER DEALINGS IN THE SOFTWARE.\\n\\nFor more information, please refer to \\n*/\\n\\n\\n/** @title BytesLib **/\\n/** @author https://github.com/GNSPS **/\\n\\nlibrary BytesLib {\\n function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) {\\n bytes memory tempBytes;\\n\\n assembly {\\n // Get a location of some free memory and store it in tempBytes as\\n // Solidity does for memory variables.\\n tempBytes := mload(0x40)\\n\\n // Store the length of the first bytes array at the beginning of\\n // the memory for tempBytes.\\n let length := mload(_preBytes)\\n mstore(tempBytes, length)\\n\\n // Maintain a memory counter for the current write location in the\\n // temp bytes array by adding the 32 bytes for the array length to\\n // the starting location.\\n let mc := add(tempBytes, 0x20)\\n // Stop copying when the memory counter reaches the length of the\\n // first bytes array.\\n let end := add(mc, length)\\n\\n for {\\n // Initialize a copy counter to the start of the _preBytes data,\\n // 32 bytes into its memory.\\n let cc := add(_preBytes, 0x20)\\n } lt(mc, end) {\\n // Increase both counters by 32 bytes each iteration.\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // Write the _preBytes data into the tempBytes memory 32 bytes\\n // at a time.\\n mstore(mc, mload(cc))\\n }\\n\\n // Add the length of _postBytes to the current length of tempBytes\\n // and store it as the new length in the first 32 bytes of the\\n // tempBytes memory.\\n length := mload(_postBytes)\\n mstore(tempBytes, add(length, mload(tempBytes)))\\n\\n // Move the memory counter back from a multiple of 0x20 to the\\n // actual end of the _preBytes data.\\n mc := end\\n // Stop copying when the memory counter reaches the new combined\\n // length of the arrays.\\n end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n } lt(mc, end) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n mstore(mc, mload(cc))\\n }\\n\\n // Update the free-memory pointer by padding our last write location\\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\\n // next 32 byte block, then round down to the nearest multiple of\\n // 32. If the sum of the length of the two arrays is zero then add\\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\\n mstore(0x40, and(\\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\\n not(31) // Round down to the nearest 32 bytes.\\n ))\\n }\\n\\n return tempBytes;\\n }\\n\\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {\\n assembly {\\n // Read the first 32 bytes of _preBytes storage, which is the length\\n // of the array. (We don't need to use the offset into the slot\\n // because arrays use the entire slot.)\\n let fslot := sload(_preBytes.slot)\\n // Arrays of 31 bytes or less have an even value in their slot,\\n // while longer arrays have an odd value. The actual length is\\n // the slot divided by two for odd values, and the lowest order\\n // byte divided by two for even values.\\n // If the slot is even, bitwise and the slot with 255 and divide by\\n // two to get the length. If the slot is odd, bitwise and the slot\\n // with -1 and divide by two.\\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\\n let mlength := mload(_postBytes)\\n let newlength := add(slength, mlength)\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n switch add(lt(slength, 32), lt(newlength, 32))\\n case 2 {\\n // Since the new array still fits in the slot, we just need to\\n // update the contents of the slot.\\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\\n sstore(\\n _preBytes.slot,\\n // all the modifications to the slot are inside this\\n // next block\\n add(\\n // we can just add to the slot contents because the\\n // bytes we want to change are the LSBs\\n fslot,\\n add(\\n mul(\\n div(\\n // load the bytes from memory\\n mload(add(_postBytes, 0x20)),\\n // zero all bytes to the right\\n exp(0x100, sub(32, mlength))\\n ),\\n // and now shift left the number of bytes to\\n // leave space for the length in the slot\\n exp(0x100, sub(32, newlength))\\n ),\\n // increase length by the double of the memory\\n // bytes length\\n mul(mlength, 2)\\n )\\n )\\n )\\n }\\n case 1 {\\n // The stored value fits in the slot, but the combined value\\n // will exceed it.\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // The contents of the _postBytes array start 32 bytes into\\n // the structure. Our first read should obtain the `submod`\\n // bytes that can fit into the unused space in the last word\\n // of the stored array. To get this, we read 32 bytes starting\\n // from `submod`, so the data we read overlaps with the array\\n // contents by `submod` bytes. Masking the lowest-order\\n // `submod` bytes allows us to add that value directly to the\\n // stored value.\\n\\n let submod := sub(32, slength)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(\\n sc,\\n add(\\n and(\\n fslot,\\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\\n ),\\n and(mload(mc), mask)\\n )\\n )\\n\\n for {\\n mc := add(mc, 0x20)\\n sc := add(sc, 1)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n default {\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n // Start copying to the last used word of the stored array.\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // Copy over the first `submod` bytes of the new data as in\\n // case 1 above.\\n let slengthmod := mod(slength, 32)\\n let mlengthmod := mod(mlength, 32)\\n let submod := sub(32, slengthmod)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\\n\\n for {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n }\\n }\\n\\n function slice(bytes memory _bytes, uint _start, uint _length) internal pure returns (bytes memory res) {\\n if (_length == 0) {\\n return hex\\\"\\\";\\n }\\n uint _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n // Alloc bytes array with additional 32 bytes afterspace and assign it's size\\n res := mload(0x40)\\n mstore(0x40, add(add(res, 64), _length))\\n mstore(res, _length)\\n\\n // Compute distance between source and destination pointers\\n let diff := sub(res, add(_bytes, _start))\\n\\n for {\\n let src := add(add(_bytes, 32), _start)\\n let end := add(src, _length)\\n } lt(src, end) {\\n src := add(src, 32)\\n } {\\n mstore(add(src, diff), mload(src))\\n }\\n }\\n }\\n\\n /// @notice Take a slice of the byte array, overwriting the destination.\\n /// The length of the slice will equal the length of the destination array.\\n /// @dev Make sure the destination array has afterspace if required.\\n /// @param _bytes The source array\\n /// @param _dest The destination array.\\n /// @param _start The location to start in the source array.\\n function sliceInPlace(\\n bytes memory _bytes,\\n bytes memory _dest,\\n uint _start\\n ) internal pure {\\n uint _length = _dest.length;\\n uint _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n for {\\n let src := add(add(_bytes, 32), _start)\\n let res := add(_dest, 32)\\n let end := add(src, _length)\\n } lt(src, end) {\\n src := add(src, 32)\\n res := add(res, 32)\\n } {\\n mstore(res, mload(src))\\n }\\n }\\n }\\n\\n // Static slice functions, no bounds checking\\n /// @notice take a 32-byte slice from the specified position\\n function slice32(bytes memory _bytes, uint _start) internal pure returns (bytes32 res) {\\n assembly {\\n res := mload(add(add(_bytes, 32), _start))\\n }\\n }\\n\\n /// @notice take a 20-byte slice from the specified position\\n function slice20(bytes memory _bytes, uint _start) internal pure returns (bytes20) {\\n return bytes20(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 8-byte slice from the specified position\\n function slice8(bytes memory _bytes, uint _start) internal pure returns (bytes8) {\\n return bytes8(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 4-byte slice from the specified position\\n function slice4(bytes memory _bytes, uint _start) internal pure returns (bytes4) {\\n return bytes4(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 3-byte slice from the specified position\\n function slice3(bytes memory _bytes, uint _start) internal pure returns (bytes3) {\\n return bytes3(slice32(_bytes, _start));\\n }\\n\\n /// @notice take a 2-byte slice from the specified position\\n function slice2(bytes memory _bytes, uint _start) internal pure returns (bytes2) {\\n return bytes2(slice32(_bytes, _start));\\n }\\n\\n function toAddress(bytes memory _bytes, uint _start) internal pure returns (address) {\\n uint _totalLen = _start + 20;\\n require(_totalLen > _start && _bytes.length >= _totalLen, \\\"Address conversion out of bounds.\\\");\\n address tempAddress;\\n\\n assembly {\\n tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)\\n }\\n\\n return tempAddress;\\n }\\n\\n function toUint(bytes memory _bytes, uint _start) internal pure returns (uint256) {\\n uint _totalLen = _start + 32;\\n require(_totalLen > _start && _bytes.length >= _totalLen, \\\"Uint conversion out of bounds.\\\");\\n uint256 tempUint;\\n\\n assembly {\\n tempUint := mload(add(add(_bytes, 0x20), _start))\\n }\\n\\n return tempUint;\\n }\\n\\n function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {\\n bool success = true;\\n\\n assembly {\\n let length := mload(_preBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(length, mload(_postBytes))\\n case 1 {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n let mc := add(_preBytes, 0x20)\\n let end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n } eq(add(lt(mc, end), cb), 2) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // if any of these checks fails then arrays are not equal\\n if iszero(eq(mload(mc), mload(cc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {\\n bool success = true;\\n\\n assembly {\\n // we know _preBytes_offset is 0\\n let fslot := sload(_preBytes.slot)\\n // Decode the length of the stored array like in concatStorage().\\n let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)\\n let mlength := mload(_postBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(slength, mlength)\\n case 1 {\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n if iszero(iszero(slength)) {\\n switch lt(slength, 32)\\n case 1 {\\n // blank the last byte which is the length\\n fslot := mul(div(fslot, 0x100), 0x100)\\n\\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\\n // unsuccess:\\n success := 0\\n }\\n }\\n default {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := keccak256(0x0, 0x20)\\n\\n let mc := add(_postBytes, 0x20)\\n let end := add(mc, mlength)\\n\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n for {} eq(add(lt(mc, end), cb), 2) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n if iszero(eq(sload(sc), mload(mc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function toBytes32(bytes memory _source) pure internal returns (bytes32 result) {\\n if (_source.length == 0) {\\n return 0x0;\\n }\\n\\n assembly {\\n result := mload(add(_source, 32))\\n }\\n }\\n\\n function keccak256Slice(bytes memory _bytes, uint _start, uint _length) pure internal returns (bytes32 result) {\\n uint _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n result := keccak256(add(add(_bytes, 32), _start), _length)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x43e0f3b3b23c861bd031588bf410dfdd02e2af17941a89aa38d70e534e0380d1\"},\"@keep-network/bitcoin-spv-sol/contracts/CheckBitcoinSigs.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/** @title CheckBitcoinSigs */\\n/** @author Summa (https://summa.one) */\\n\\nimport {BytesLib} from \\\"./BytesLib.sol\\\";\\nimport {BTCUtils} from \\\"./BTCUtils.sol\\\";\\n\\n\\nlibrary CheckBitcoinSigs {\\n\\n using BytesLib for bytes;\\n using BTCUtils for bytes;\\n\\n /// @notice Derives an Ethereum Account address from a pubkey\\n /// @dev The address is the last 20 bytes of the keccak256 of the address\\n /// @param _pubkey The public key X & Y. Unprefixed, as a 64-byte array\\n /// @return The account address\\n function accountFromPubkey(bytes memory _pubkey) internal pure returns (address) {\\n require(_pubkey.length == 64, \\\"Pubkey must be 64-byte raw, uncompressed key.\\\");\\n\\n // keccak hash of uncompressed unprefixed pubkey\\n bytes32 _digest = keccak256(_pubkey);\\n return address(uint160(uint256(_digest)));\\n }\\n\\n /// @notice Calculates the p2wpkh output script of a pubkey\\n /// @dev Compresses keys to 33 bytes as required by Bitcoin\\n /// @param _pubkey The public key, compressed or uncompressed\\n /// @return The p2wkph output script\\n function p2wpkhFromPubkey(bytes memory _pubkey) internal view returns (bytes memory) {\\n bytes memory _compressedPubkey;\\n uint8 _prefix;\\n\\n if (_pubkey.length == 64) {\\n _prefix = uint8(_pubkey[_pubkey.length - 1]) % 2 == 1 ? 3 : 2;\\n _compressedPubkey = abi.encodePacked(_prefix, _pubkey.slice32(0));\\n } else if (_pubkey.length == 65) {\\n _prefix = uint8(_pubkey[_pubkey.length - 1]) % 2 == 1 ? 3 : 2;\\n _compressedPubkey = abi.encodePacked(_prefix, _pubkey.slice32(1));\\n } else {\\n _compressedPubkey = _pubkey;\\n }\\n\\n require(_compressedPubkey.length == 33, \\\"Witness PKH requires compressed keys\\\");\\n\\n bytes20 _pubkeyHash = _compressedPubkey.hash160View();\\n return abi.encodePacked(hex\\\"0014\\\", _pubkeyHash);\\n }\\n\\n /// @notice checks a signed message's validity under a pubkey\\n /// @dev does this using ecrecover because Ethereum has no soul\\n /// @param _pubkey the public key to check (64 bytes)\\n /// @param _digest the message digest signed\\n /// @param _v the signature recovery value\\n /// @param _r the signature r value\\n /// @param _s the signature s value\\n /// @return true if signature is valid, else false\\n function checkSig(\\n bytes memory _pubkey,\\n bytes32 _digest,\\n uint8 _v,\\n bytes32 _r,\\n bytes32 _s\\n ) internal pure returns (bool) {\\n require(_pubkey.length == 64, \\\"Requires uncompressed unprefixed pubkey\\\");\\n address _expected = accountFromPubkey(_pubkey);\\n address _actual = ecrecover(_digest, _v, _r, _s);\\n return _actual == _expected;\\n }\\n\\n /// @notice checks a signed message against a bitcoin p2wpkh output script\\n /// @dev does this my verifying the p2wpkh matches an ethereum account\\n /// @param _p2wpkhOutputScript the bitcoin output script\\n /// @param _pubkey the uncompressed, unprefixed public key to check\\n /// @param _digest the message digest signed\\n /// @param _v the signature recovery value\\n /// @param _r the signature r value\\n /// @param _s the signature s value\\n /// @return true if signature is valid, else false\\n function checkBitcoinSig(\\n bytes memory _p2wpkhOutputScript,\\n bytes memory _pubkey,\\n bytes32 _digest,\\n uint8 _v,\\n bytes32 _r,\\n bytes32 _s\\n ) internal view returns (bool) {\\n require(_pubkey.length == 64, \\\"Requires uncompressed unprefixed pubkey\\\");\\n\\n bool _isExpectedSigner = keccak256(p2wpkhFromPubkey(_pubkey)) == keccak256(_p2wpkhOutputScript); // is it the expected signer?\\n if (!_isExpectedSigner) {return false;}\\n\\n bool _sigResult = checkSig(_pubkey, _digest, _v, _r, _s);\\n return _sigResult;\\n }\\n\\n /// @notice checks if a message is the sha256 preimage of a digest\\n /// @dev this is NOT the hash256! this step is necessary for ECDSA security!\\n /// @param _digest the digest\\n /// @param _candidate the purported preimage\\n /// @return true if the preimage matches the digest, else false\\n function isSha256Preimage(\\n bytes memory _candidate,\\n bytes32 _digest\\n ) internal pure returns (bool) {\\n return sha256(_candidate) == _digest;\\n }\\n\\n /// @notice checks if a message is the keccak256 preimage of a digest\\n /// @dev this step is necessary for ECDSA security!\\n /// @param _digest the digest\\n /// @param _candidate the purported preimage\\n /// @return true if the preimage matches the digest, else false\\n function isKeccak256Preimage(\\n bytes memory _candidate,\\n bytes32 _digest\\n ) internal pure returns (bool) {\\n return keccak256(_candidate) == _digest;\\n }\\n\\n /// @notice calculates the signature hash of a Bitcoin transaction with the provided details\\n /// @dev documented in bip143. many values are hardcoded here\\n /// @param _outpoint the bitcoin UTXO id (32-byte txid + 4-byte output index)\\n /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey))\\n /// @param _inputValue the value of the input in satoshi\\n /// @param _outputValue the value of the output in satoshi\\n /// @param _outputScript the length-prefixed output script\\n /// @return the double-sha256 (hash256) signature hash as defined by bip143\\n function wpkhSpendSighash(\\n bytes memory _outpoint, // 36-byte UTXO id\\n bytes20 _inputPKH, // 20-byte hash160\\n bytes8 _inputValue, // 8-byte LE\\n bytes8 _outputValue, // 8-byte LE\\n bytes memory _outputScript // lenght-prefixed output script\\n ) internal view returns (bytes32) {\\n // Fixes elements to easily make a 1-in 1-out sighash digest\\n // Does not support timelocks\\n // bytes memory _scriptCode = abi.encodePacked(\\n // hex\\\"1976a914\\\", // length, dup, hash160, pkh_length\\n // _inputPKH,\\n // hex\\\"88ac\\\"); // equal, checksig\\n\\n bytes32 _hashOutputs = abi.encodePacked(\\n _outputValue, // 8-byte LE\\n _outputScript).hash256View();\\n\\n bytes memory _sighashPreimage = abi.encodePacked(\\n hex\\\"01000000\\\", // version\\n _outpoint.hash256View(), // hashPrevouts\\n hex\\\"8cb9012517c817fead650287d61bdd9c68803b6bf9c64133dcab3e65b5a50cb9\\\", // hashSequence(00000000)\\n _outpoint, // outpoint\\n // p2wpkh script code\\n hex\\\"1976a914\\\", // length, dup, hash160, pkh_length\\n _inputPKH,\\n hex\\\"88ac\\\", // equal, checksig\\n // end script code\\n _inputValue, // value of the input in 8-byte LE\\n hex\\\"00000000\\\", // input nSequence\\n _hashOutputs, // hash of the single output\\n hex\\\"00000000\\\", // nLockTime\\n hex\\\"01000000\\\" // SIGHASH_ALL\\n );\\n return _sighashPreimage.hash256View();\\n }\\n\\n /// @notice calculates the signature hash of a Bitcoin transaction with the provided details\\n /// @dev documented in bip143. many values are hardcoded here\\n /// @param _outpoint the bitcoin UTXO id (32-byte txid + 4-byte output index)\\n /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey))\\n /// @param _inputValue the value of the input in satoshi\\n /// @param _outputValue the value of the output in satoshi\\n /// @param _outputPKH the output pubkeyhash (hash160(recipient_pubkey))\\n /// @return the double-sha256 (hash256) signature hash as defined by bip143\\n function wpkhToWpkhSighash(\\n bytes memory _outpoint, // 36-byte UTXO id\\n bytes20 _inputPKH, // 20-byte hash160\\n bytes8 _inputValue, // 8-byte LE\\n bytes8 _outputValue, // 8-byte LE\\n bytes20 _outputPKH // 20-byte hash160\\n ) internal view returns (bytes32) {\\n return wpkhSpendSighash(\\n _outpoint,\\n _inputPKH,\\n _inputValue,\\n _outputValue,\\n abi.encodePacked(\\n hex\\\"160014\\\", // wpkh tag\\n _outputPKH)\\n );\\n }\\n\\n /// @notice Preserved for API compatibility with older version\\n /// @dev documented in bip143. many values are hardcoded here\\n /// @param _outpoint the bitcoin UTXO id (32-byte txid + 4-byte output index)\\n /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey))\\n /// @param _inputValue the value of the input in satoshi\\n /// @param _outputValue the value of the output in satoshi\\n /// @param _outputPKH the output pubkeyhash (hash160(recipient_pubkey))\\n /// @return the double-sha256 (hash256) signature hash as defined by bip143\\n function oneInputOneOutputSighash(\\n bytes memory _outpoint, // 36-byte UTXO id\\n bytes20 _inputPKH, // 20-byte hash160\\n bytes8 _inputValue, // 8-byte LE\\n bytes8 _outputValue, // 8-byte LE\\n bytes20 _outputPKH // 20-byte hash160\\n ) internal view returns (bytes32) {\\n return wpkhToWpkhSighash(_outpoint, _inputPKH, _inputValue, _outputValue, _outputPKH);\\n }\\n\\n}\\n\",\"keccak256\":\"0xfffbd5486af77058fe9385d63d433da914a043994b1affdfcb87248aa10a234c\"},\"@keep-network/bitcoin-spv-sol/contracts/SafeMath.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/*\\nThe MIT License (MIT)\\n\\nCopyright (c) 2016 Smart Contract Solutions, Inc.\\n\\nPermission is hereby granted, free of charge, to any person obtaining\\na copy of this software and associated documentation files (the\\n\\\"Software\\\"), to deal in the Software without restriction, including\\nwithout limitation the rights to use, copy, modify, merge, publish,\\ndistribute, sublicense, and/or sell copies of the Software, and to\\npermit persons to whom the Software is furnished to do so, subject to\\nthe following conditions:\\n\\nThe above copyright notice and this permission notice shall be included\\nin all copies or substantial portions of the Software.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\\n*/\\n\\n\\n/**\\n * @title SafeMath\\n * @dev Math operations with safety checks that throw on error\\n */\\nlibrary SafeMath {\\n\\n /**\\n * @dev Multiplies two numbers, throws on overflow.\\n */\\n function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {\\n // Gas optimization: this is cheaper than asserting 'a' not being zero, but the\\n // benefit is lost if 'b' is also tested.\\n // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522\\n if (_a == 0) {\\n return 0;\\n }\\n\\n c = _a * _b;\\n require(c / _a == _b, \\\"Overflow during multiplication.\\\");\\n return c;\\n }\\n\\n /**\\n * @dev Integer division of two numbers, truncating the quotient.\\n */\\n function div(uint256 _a, uint256 _b) internal pure returns (uint256) {\\n // assert(_b > 0); // Solidity automatically throws when dividing by 0\\n // uint256 c = _a / _b;\\n // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold\\n return _a / _b;\\n }\\n\\n /**\\n * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).\\n */\\n function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {\\n require(_b <= _a, \\\"Underflow during subtraction.\\\");\\n return _a - _b;\\n }\\n\\n /**\\n * @dev Adds two numbers, throws on overflow.\\n */\\n function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {\\n c = _a + _b;\\n require(c >= _a, \\\"Overflow during addition.\\\");\\n return c;\\n }\\n}\\n\",\"keccak256\":\"0x35930d982394c7ffde439b82e5e696c5b21a6f09699d44861dfe409ef64084a3\"},\"@keep-network/bitcoin-spv-sol/contracts/ValidateSPV.sol\":{\"content\":\"pragma solidity ^0.8.4;\\n\\n/** @title ValidateSPV*/\\n/** @author Summa (https://summa.one) */\\n\\nimport {BytesLib} from \\\"./BytesLib.sol\\\";\\nimport {SafeMath} from \\\"./SafeMath.sol\\\";\\nimport {BTCUtils} from \\\"./BTCUtils.sol\\\";\\n\\n\\nlibrary ValidateSPV {\\n\\n using BTCUtils for bytes;\\n using BTCUtils for uint256;\\n using BytesLib for bytes;\\n using SafeMath for uint256;\\n\\n enum InputTypes { NONE, LEGACY, COMPATIBILITY, WITNESS }\\n enum OutputTypes { NONE, WPKH, WSH, OP_RETURN, PKH, SH, NONSTANDARD }\\n\\n uint256 constant ERR_BAD_LENGTH = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;\\n uint256 constant ERR_INVALID_CHAIN = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe;\\n uint256 constant ERR_LOW_WORK = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd;\\n\\n function getErrBadLength() internal pure returns (uint256) {\\n return ERR_BAD_LENGTH;\\n }\\n\\n function getErrInvalidChain() internal pure returns (uint256) {\\n return ERR_INVALID_CHAIN;\\n }\\n\\n function getErrLowWork() internal pure returns (uint256) {\\n return ERR_LOW_WORK;\\n }\\n\\n /// @notice Validates a tx inclusion in the block\\n /// @dev `index` is not a reliable indicator of location within a block\\n /// @param _txid The txid (LE)\\n /// @param _merkleRoot The merkle root (as in the block header)\\n /// @param _intermediateNodes The proof's intermediate nodes (digests between leaf and root)\\n /// @param _index The leaf's index in the tree (0-indexed)\\n /// @return true if fully valid, false otherwise\\n function prove(\\n bytes32 _txid,\\n bytes32 _merkleRoot,\\n bytes memory _intermediateNodes,\\n uint _index\\n ) internal view returns (bool) {\\n // Shortcut the empty-block case\\n if (_txid == _merkleRoot && _index == 0 && _intermediateNodes.length == 0) {\\n return true;\\n }\\n\\n // If the Merkle proof failed, bubble up error\\n return BTCUtils.verifyHash256Merkle(\\n _txid,\\n _intermediateNodes,\\n _merkleRoot,\\n _index\\n );\\n }\\n\\n /// @notice Hashes transaction to get txid\\n /// @dev Supports Legacy and Witness\\n /// @param _version 4-bytes version\\n /// @param _vin Raw bytes length-prefixed input vector\\n /// @param _vout Raw bytes length-prefixed output vector\\n /// @param _locktime 4-byte tx locktime\\n /// @return 32-byte transaction id, little endian\\n function calculateTxId(\\n bytes4 _version,\\n bytes memory _vin,\\n bytes memory _vout,\\n bytes4 _locktime\\n ) internal view returns (bytes32) {\\n // Get transaction hash double-Sha256(version + nIns + inputs + nOuts + outputs + locktime)\\n return abi.encodePacked(_version, _vin, _vout, _locktime).hash256View();\\n }\\n\\n /// @notice Checks validity of header chain\\n /// @notice Compares the hash of each header to the prevHash in the next header\\n /// @param headers Raw byte array of header chain\\n /// @return totalDifficulty The total accumulated difficulty of the header chain, or an error code\\n function validateHeaderChain(\\n bytes memory headers\\n ) internal view returns (uint256 totalDifficulty) {\\n\\n // Check header chain length\\n if (headers.length % 80 != 0) {return ERR_BAD_LENGTH;}\\n\\n // Initialize header start index\\n bytes32 digest;\\n\\n totalDifficulty = 0;\\n\\n for (uint256 start = 0; start < headers.length; start += 80) {\\n\\n // After the first header, check that headers are in a chain\\n if (start != 0) {\\n if (!validateHeaderPrevHash(headers, start, digest)) {return ERR_INVALID_CHAIN;}\\n }\\n\\n // ith header target\\n uint256 target = headers.extractTargetAt(start);\\n\\n // Require that the header has sufficient work\\n digest = headers.hash256Slice(start, 80);\\n if(uint256(digest).reverseUint256() > target) {\\n return ERR_LOW_WORK;\\n }\\n\\n // Add ith header difficulty to difficulty sum\\n totalDifficulty = totalDifficulty + target.calculateDifficulty();\\n }\\n }\\n\\n /// @notice Checks validity of header work\\n /// @param digest Header digest\\n /// @param target The target threshold\\n /// @return true if header work is valid, false otherwise\\n function validateHeaderWork(\\n bytes32 digest,\\n uint256 target\\n ) internal pure returns (bool) {\\n if (digest == bytes32(0)) {return false;}\\n return (uint256(digest).reverseUint256() < target);\\n }\\n\\n /// @notice Checks validity of header chain\\n /// @dev Compares current header prevHash to previous header's digest\\n /// @param headers The raw bytes array containing the header\\n /// @param at The position of the header\\n /// @param prevHeaderDigest The previous header's digest\\n /// @return true if the connect is valid, false otherwise\\n function validateHeaderPrevHash(\\n bytes memory headers,\\n uint256 at,\\n bytes32 prevHeaderDigest\\n ) internal pure returns (bool) {\\n\\n // Extract prevHash of current header\\n bytes32 prevHash = headers.extractPrevBlockLEAt(at);\\n\\n // Compare prevHash of current header to previous header's digest\\n if (prevHash != prevHeaderDigest) {return false;}\\n\\n return true;\\n }\\n}\\n\",\"keccak256\":\"0xce3febbf3ad3a7ff8a8effd0c7ccaf7ccfa2719578b537d49ea196f0bae8062b\"},\"@keep-network/ecdsa/contracts/EcdsaDkgValidator.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\n// Initial version copied from Keep Network Random Beacon:\\n// https://github.com/keep-network/keep-core/blob/5138c7628868dbeed3ae2164f76fccc6c1fbb9e8/solidity/random-beacon/contracts/DKGValidator.sol\\n//\\n// With the following differences:\\n// - group public key length,\\n// - group size and related thresholds,\\n// - documentation.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/libraries/BytesLib.sol\\\";\\nimport \\\"@keep-network/sortition-pools/contracts/SortitionPool.sol\\\";\\nimport \\\"./libraries/EcdsaDkg.sol\\\";\\n\\n/// @title DKG result validator\\n/// @notice EcdsaDkgValidator allows performing a full validation of DKG result,\\n/// including checking the format of fields in the result, declared\\n/// selected group members, and signatures of operators supporting the\\n/// result. The operator submitting the result should perform the\\n/// validation using a free contract call before submitting the result\\n/// to ensure their result is valid and can not be challenged. All other\\n/// network operators should perform validation of the submitted result\\n/// using a free contract call and challenge the result if the\\n/// validation fails.\\ncontract EcdsaDkgValidator {\\n using BytesLib for bytes;\\n using ECDSA for bytes32;\\n\\n /// @dev Size of a group in DKG.\\n uint256 public constant groupSize = 100;\\n\\n /// @dev The minimum number of group members needed to interact according to\\n /// the protocol to produce a signature. The adversary can not learn\\n /// anything about the key as long as it does not break into\\n /// groupThreshold+1 of members.\\n uint256 public constant groupThreshold = 51;\\n\\n /// @dev The minimum number of active and properly behaving group members\\n /// during the DKG needed to accept the result. This number is higher\\n /// than `groupThreshold` to keep a safety margin for members becoming\\n /// inactive after DKG so that the group can still produce signature.\\n uint256 public constant activeThreshold = 90; // 90% of groupSize\\n\\n /// @dev Size in bytes of a public key produced by group members during the\\n /// the DKG. The length assumes uncompressed ECDSA public key.\\n uint256 public constant publicKeyByteSize = 64;\\n\\n /// @dev Size in bytes of a single signature produced by operator supporting\\n /// DKG result.\\n uint256 public constant signatureByteSize = 65;\\n\\n SortitionPool public immutable sortitionPool;\\n\\n constructor(SortitionPool _sortitionPool) {\\n sortitionPool = _sortitionPool;\\n }\\n\\n /// @notice Performs a full validation of DKG result, including checking the\\n /// format of fields in the result, declared selected group members,\\n /// and signatures of operators supporting the result.\\n /// @param seed seed used to start the DKG and select group members\\n /// @param startBlock DKG start block\\n /// @return isValid true if the result is valid, false otherwise\\n /// @return errorMsg validation error message; empty for a valid result\\n function validate(\\n EcdsaDkg.Result calldata result,\\n uint256 seed,\\n uint256 startBlock\\n ) external view returns (bool isValid, string memory errorMsg) {\\n (bool hasValidFields, string memory error) = validateFields(result);\\n if (!hasValidFields) {\\n return (false, error);\\n }\\n\\n if (!validateSignatures(result, startBlock)) {\\n return (false, \\\"Invalid signatures\\\");\\n }\\n\\n if (!validateGroupMembers(result, seed)) {\\n return (false, \\\"Invalid group members\\\");\\n }\\n\\n // At this point all group members and misbehaved members were verified\\n if (!validateMembersHash(result)) {\\n return (false, \\\"Invalid members hash\\\");\\n }\\n\\n return (true, \\\"\\\");\\n }\\n\\n /// @notice Performs a static validation of DKG result fields: lengths,\\n /// ranges, and order of arrays.\\n /// @return isValid true if the result is valid, false otherwise\\n /// @return errorMsg validation error message; empty for a valid result\\n function validateFields(EcdsaDkg.Result calldata result)\\n public\\n pure\\n returns (bool isValid, string memory errorMsg)\\n {\\n if (result.groupPubKey.length != publicKeyByteSize) {\\n return (false, \\\"Malformed group public key\\\");\\n }\\n\\n // The number of misbehaved members can not exceed the threshold.\\n // Misbehaved member indices needs to be unique, between [1, groupSize],\\n // and sorted in ascending order.\\n uint8[] calldata misbehavedMembersIndices = result\\n .misbehavedMembersIndices;\\n if (groupSize - misbehavedMembersIndices.length < activeThreshold) {\\n return (false, \\\"Too many members misbehaving during DKG\\\");\\n }\\n if (misbehavedMembersIndices.length > 1) {\\n if (\\n misbehavedMembersIndices[0] < 1 ||\\n misbehavedMembersIndices[misbehavedMembersIndices.length - 1] >\\n groupSize\\n ) {\\n return (false, \\\"Corrupted misbehaved members indices\\\");\\n }\\n for (uint256 i = 1; i < misbehavedMembersIndices.length; i++) {\\n if (\\n misbehavedMembersIndices[i - 1] >=\\n misbehavedMembersIndices[i]\\n ) {\\n return (false, \\\"Corrupted misbehaved members indices\\\");\\n }\\n }\\n }\\n\\n // Each signature needs to have a correct length and signatures need to\\n // be provided.\\n uint256 signaturesCount = result.signatures.length / signatureByteSize;\\n if (result.signatures.length == 0) {\\n return (false, \\\"No signatures provided\\\");\\n }\\n if (result.signatures.length % signatureByteSize != 0) {\\n return (false, \\\"Malformed signatures array\\\");\\n }\\n\\n // We expect the same amount of signatures as the number of declared\\n // group member indices that signed the result.\\n uint256[] calldata signingMembersIndices = result.signingMembersIndices;\\n if (signaturesCount != signingMembersIndices.length) {\\n return (false, \\\"Unexpected signatures count\\\");\\n }\\n if (signaturesCount < groupThreshold) {\\n return (false, \\\"Too few signatures\\\");\\n }\\n if (signaturesCount > groupSize) {\\n return (false, \\\"Too many signatures\\\");\\n }\\n\\n // Signing member indices needs to be unique, between [1,groupSize],\\n // and sorted in ascending order.\\n if (\\n signingMembersIndices[0] < 1 ||\\n signingMembersIndices[signingMembersIndices.length - 1] > groupSize\\n ) {\\n return (false, \\\"Corrupted signing member indices\\\");\\n }\\n for (uint256 i = 1; i < signingMembersIndices.length; i++) {\\n if (signingMembersIndices[i - 1] >= signingMembersIndices[i]) {\\n return (false, \\\"Corrupted signing member indices\\\");\\n }\\n }\\n\\n return (true, \\\"\\\");\\n }\\n\\n /// @notice Performs validation of group members as declared in DKG\\n /// result against group members selected by the sortition pool.\\n /// @param seed seed used to start the DKG and select group members\\n /// @return true if group members matches; false otherwise\\n function validateGroupMembers(EcdsaDkg.Result calldata result, uint256 seed)\\n public\\n view\\n returns (bool)\\n {\\n uint32[] calldata resultMembers = result.members;\\n uint32[] memory actualGroupMembers = sortitionPool.selectGroup(\\n groupSize,\\n bytes32(seed)\\n );\\n if (resultMembers.length != actualGroupMembers.length) {\\n return false;\\n }\\n for (uint256 i = 0; i < resultMembers.length; i++) {\\n if (resultMembers[i] != actualGroupMembers[i]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n /// @notice Performs validation of signatures supplied in DKG result.\\n /// Note that this function does not check if addresses which\\n /// supplied signatures supporting the result are the ones selected\\n /// to the group by sortition pool. This function should be used\\n /// together with `validateGroupMembers`.\\n /// @param startBlock DKG start block\\n /// @return true if group members matches; false otherwise\\n function validateSignatures(\\n EcdsaDkg.Result calldata result,\\n uint256 startBlock\\n ) public view returns (bool) {\\n bytes32 hash = keccak256(\\n abi.encode(\\n block.chainid,\\n result.groupPubKey,\\n result.misbehavedMembersIndices,\\n startBlock\\n )\\n ).toEthSignedMessageHash();\\n\\n uint256[] calldata signingMembersIndices = result.signingMembersIndices;\\n uint32[] memory signingMemberIds = new uint32[](\\n signingMembersIndices.length\\n );\\n for (uint256 i = 0; i < signingMembersIndices.length; i++) {\\n signingMemberIds[i] = result.members[signingMembersIndices[i] - 1];\\n }\\n\\n address[] memory signingMemberAddresses = sortitionPool.getIDOperators(\\n signingMemberIds\\n );\\n\\n bytes memory current; // Current signature to be checked.\\n\\n uint256 signaturesCount = result.signatures.length / signatureByteSize;\\n for (uint256 i = 0; i < signaturesCount; i++) {\\n current = result.signatures.slice(\\n signatureByteSize * i,\\n signatureByteSize\\n );\\n address recoveredAddress = hash.recover(current);\\n\\n if (signingMemberAddresses[i] != recoveredAddress) {\\n return false;\\n }\\n }\\n\\n return true;\\n }\\n\\n /// @notice Performs validation of hashed group members that actively took\\n /// part in DKG.\\n /// @param result DKG result\\n /// @return true if calculated result's group members hash matches with the\\n /// one that is challenged.\\n function validateMembersHash(EcdsaDkg.Result calldata result)\\n public\\n pure\\n returns (bool)\\n {\\n if (result.misbehavedMembersIndices.length > 0) {\\n // members that generated a group signing key\\n uint32[] memory groupMembers = new uint32[](\\n result.members.length - result.misbehavedMembersIndices.length\\n );\\n uint256 k = 0; // misbehaved members counter\\n uint256 j = 0; // group members counter\\n for (uint256 i = 0; i < result.members.length; i++) {\\n // misbehaved member indices start from 1, so we need to -1 on misbehaved\\n if (i != result.misbehavedMembersIndices[k] - 1) {\\n groupMembers[j] = result.members[i];\\n j++;\\n } else if (k < result.misbehavedMembersIndices.length - 1) {\\n k++;\\n }\\n }\\n\\n return keccak256(abi.encode(groupMembers)) == result.membersHash;\\n }\\n\\n return keccak256(abi.encode(result.members)) == result.membersHash;\\n }\\n}\\n\",\"keccak256\":\"0xe8f3d63ef4213ac71d447726be3971c5ed6b0b0eb145763d324faecdce707bf6\",\"license\":\"GPL-3.0-only\"},\"@keep-network/ecdsa/contracts/api/IWalletOwner.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\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\",\"keccak256\":\"0x8d4654269ab20710e8a729c225c2c69edae7f01ddbd5e037ab591df65e32faa8\",\"license\":\"GPL-3.0-only\"},\"@keep-network/ecdsa/contracts/api/IWalletRegistry.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"../libraries/EcdsaDkg.sol\\\";\\n\\ninterface IWalletRegistry {\\n /// @notice Requests a new wallet creation.\\n /// @dev Only the Wallet Owner can call this function.\\n function requestNewWallet() external;\\n\\n /// @notice Closes an existing wallet.\\n /// @param walletID ID of the wallet.\\n /// @dev Only the Wallet Owner can call this function.\\n function closeWallet(bytes32 walletID) external;\\n\\n /// @notice Adds all signing group members of the wallet with the given ID\\n /// to the slashing queue of the staking contract. The notifier will\\n /// receive reward per each group member from the staking contract\\n /// notifiers treasury. The reward is scaled by the\\n /// `rewardMultiplier` provided as a parameter.\\n /// @param amount Amount of tokens to seize from each signing group member\\n /// @param rewardMultiplier Fraction of the staking contract notifiers\\n /// reward the notifier should receive; should be between [0, 100]\\n /// @param notifier Address of the misbehavior notifier\\n /// @param walletID ID of the wallet\\n /// @param walletMembersIDs Identifiers of the wallet signing group members\\n /// @dev Only the Wallet Owner can call this function.\\n /// Requirements:\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events.\\n /// - `rewardMultiplier` must be between [0, 100].\\n /// - This function does revert if staking contract call reverts.\\n /// The calling code needs to handle the potential revert.\\n function seize(\\n uint96 amount,\\n uint256 rewardMultiplier,\\n address notifier,\\n bytes32 walletID,\\n uint32[] calldata walletMembersIDs\\n ) external;\\n\\n /// @notice Gets public key of a wallet with a given wallet ID.\\n /// The public key is returned in an uncompressed format as a 64-byte\\n /// concatenation of X and Y coordinates.\\n /// @param walletID ID of the wallet.\\n /// @return Uncompressed public key of the wallet.\\n function getWalletPublicKey(bytes32 walletID)\\n external\\n view\\n returns (bytes memory);\\n\\n /// @notice Check current wallet creation state.\\n function getWalletCreationState() external view returns (EcdsaDkg.State);\\n\\n /// @notice Checks whether the given operator is a member of the given\\n /// wallet signing group.\\n /// @param walletID ID of the wallet\\n /// @param walletMembersIDs Identifiers of the wallet signing group members\\n /// @param operator Address of the checked operator\\n /// @param walletMemberIndex Position of the operator in the wallet signing\\n /// group members list\\n /// @return True - if the operator is a member of the given wallet signing\\n /// group. False - otherwise.\\n /// @dev Requirements:\\n /// - The `operator` parameter must be an actual sortition pool operator.\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events.\\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length]\\n function isWalletMember(\\n bytes32 walletID,\\n uint32[] calldata walletMembersIDs,\\n address operator,\\n uint256 walletMemberIndex\\n ) external view returns (bool);\\n}\\n\",\"keccak256\":\"0xb97ece7c12601396fed705386a4e3337ee3a4809dca090a5acb62c2949337c68\",\"license\":\"GPL-3.0-only\"},\"@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n//\\n\\n// Initial version copied from Keep Network Random Beacon:\\n// https://github.com/keep-network/keep-core/blob/5138c7628868dbeed3ae2164f76fccc6c1fbb9e8/solidity/random-beacon/contracts/libraries/DKG.sol\\n//\\n// With the following differences:\\n// - the group size was set to 100,\\n// - offchainDkgTimeout was removed,\\n// - submission eligibility verification is not performed on-chain,\\n// - submission eligibility delay was replaced with a submission timeout,\\n// - seed timeout notification requires seedTimeout period to pass.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol\\\";\\nimport \\\"@keep-network/sortition-pools/contracts/SortitionPool.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/libraries/BytesLib.sol\\\";\\nimport \\\"../EcdsaDkgValidator.sol\\\";\\n\\nlibrary EcdsaDkg {\\n using BytesLib for bytes;\\n using ECDSAUpgradeable for bytes32;\\n\\n struct Parameters {\\n // Time in blocks during which a seed is expected to be delivered.\\n // DKG starts only after a seed is delivered. The time the contract\\n // awaits for a seed is not included in the DKG timeout.\\n uint256 seedTimeout;\\n // Time in blocks during which a submitted result can be challenged.\\n uint256 resultChallengePeriodLength;\\n // Extra gas required to be left at the end of the challenge DKG result\\n // transaction.\\n uint256 resultChallengeExtraGas;\\n // Time in blocks during which a result is expected to be submitted.\\n uint256 resultSubmissionTimeout;\\n // Time in blocks during which only the result submitter is allowed to\\n // approve it. Once this period ends and the submitter have not approved\\n // the result, anyone can do it.\\n uint256 submitterPrecedencePeriodLength;\\n // This struct doesn't contain `__gap` property as the structure is\\n // stored inside `Data` struct, that already have a gap that can be used\\n // on upgrade.\\n }\\n\\n struct Data {\\n // Address of the Sortition Pool contract.\\n SortitionPool sortitionPool;\\n // Address of the EcdsaDkgValidator contract.\\n EcdsaDkgValidator dkgValidator;\\n // DKG parameters. The parameters should persist between DKG executions.\\n // They should be updated with dedicated set functions only when DKG is not\\n // in progress.\\n Parameters parameters;\\n // Time in block at which DKG state was locked.\\n uint256 stateLockBlock;\\n // Time in blocks at which DKG started.\\n uint256 startBlock;\\n // Seed used to start DKG.\\n uint256 seed;\\n // Time in blocks that should be added to result submission eligibility\\n // delay calculation. It is used in case of a challenge to adjust\\n // DKG timeout calculation.\\n uint256 resultSubmissionStartBlockOffset;\\n // Hash of submitted DKG result.\\n bytes32 submittedResultHash;\\n // Block number from the moment of the DKG result submission.\\n uint256 submittedResultBlock;\\n // Reserved storage space in case we need to add more variables.\\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n // slither-disable-next-line unused-state\\n uint256[38] __gap;\\n }\\n\\n /// @notice DKG result.\\n struct Result {\\n // Claimed submitter candidate group member index.\\n // Must be in range [1, groupSize].\\n uint256 submitterMemberIndex;\\n // Generated candidate group public key\\n bytes groupPubKey;\\n // Array of misbehaved members indices (disqualified or inactive).\\n // Indices must be in range [1, groupSize], unique, and sorted in ascending\\n // order.\\n uint8[] misbehavedMembersIndices;\\n // Concatenation of signatures from members supporting the result.\\n // The message to be signed by each member is keccak256 hash of the\\n // calculated group public key, misbehaved members indices and DKG\\n // start block. The calculated hash should be prefixed with prefixed with\\n // `\\\\x19Ethereum signed message:\\\\n` before signing, so the message to\\n // sign is:\\n // `\\\\x19Ethereum signed message:\\\\n${keccak256(\\n // groupPubKey, misbehavedMembersIndices, dkgStartBlock\\n // )}`\\n bytes signatures;\\n // Indices of members corresponding to each signature. Indices must be\\n // be in range [1, groupSize], unique, and sorted in ascending order.\\n uint256[] signingMembersIndices;\\n // Identifiers of candidate group members as outputted by the group\\n // selection protocol.\\n uint32[] members;\\n // Keccak256 hash of group members identifiers that actively took part\\n // in DKG (excluding IA/DQ members).\\n bytes32 membersHash;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\n }\\n\\n /// @notice States for phases of group creation. The states doesn't include\\n /// timeouts which should be tracked and notified individually.\\n enum State {\\n // Group creation is not in progress. It is a state set after group creation\\n // completion either by timeout or by a result approval.\\n IDLE,\\n // Group creation is awaiting the seed and sortition pool is locked.\\n AWAITING_SEED,\\n // DKG protocol execution is in progress. A result is being calculated\\n // by the clients in this state and the contract awaits a result submission.\\n // This is a state to which group creation returns in case of a result\\n // challenge notification.\\n AWAITING_RESULT,\\n // DKG result was submitted and awaits an approval or a challenge. If a result\\n // gets challenge the state returns to `AWAITING_RESULT`. If a result gets\\n // approval the state changes to `IDLE`.\\n CHALLENGE\\n }\\n\\n /// @dev Size of a group in ECDSA wallet.\\n uint256 public constant groupSize = 100;\\n\\n event DkgStarted(uint256 indexed seed);\\n\\n // To recreate the members that actively took part in dkg, the selected members\\n // array should be filtered out from misbehavedMembersIndices.\\n event DkgResultSubmitted(\\n bytes32 indexed resultHash,\\n uint256 indexed seed,\\n Result result\\n );\\n\\n event DkgTimedOut();\\n\\n event DkgResultApproved(\\n bytes32 indexed resultHash,\\n address indexed approver\\n );\\n\\n event DkgResultChallenged(\\n bytes32 indexed resultHash,\\n address indexed challenger,\\n string reason\\n );\\n\\n event DkgStateLocked();\\n\\n event DkgSeedTimedOut();\\n\\n /// @notice Initializes SortitionPool and EcdsaDkgValidator addresses.\\n /// Can be performed only once.\\n /// @param _sortitionPool Sortition Pool reference\\n /// @param _dkgValidator EcdsaDkgValidator reference\\n function init(\\n Data storage self,\\n SortitionPool _sortitionPool,\\n EcdsaDkgValidator _dkgValidator\\n ) internal {\\n require(\\n address(self.sortitionPool) == address(0),\\n \\\"Sortition Pool address already set\\\"\\n );\\n\\n require(\\n address(self.dkgValidator) == address(0),\\n \\\"DKG Validator address already set\\\"\\n );\\n\\n self.sortitionPool = _sortitionPool;\\n self.dkgValidator = _dkgValidator;\\n }\\n\\n /// @notice Determines the current state of group creation. It doesn't take\\n /// timeouts into consideration. The timeouts should be tracked and\\n /// notified separately.\\n function currentState(Data storage self)\\n internal\\n view\\n returns (State state)\\n {\\n state = State.IDLE;\\n\\n if (self.sortitionPool.isLocked()) {\\n state = State.AWAITING_SEED;\\n\\n if (self.startBlock > 0) {\\n state = State.AWAITING_RESULT;\\n\\n if (self.submittedResultBlock > 0) {\\n state = State.CHALLENGE;\\n }\\n }\\n }\\n }\\n\\n /// @notice Locks the sortition pool and starts awaiting for the\\n /// group creation seed.\\n function lockState(Data storage self) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n emit DkgStateLocked();\\n\\n self.sortitionPool.lock();\\n\\n self.stateLockBlock = block.number;\\n }\\n\\n function start(Data storage self, uint256 seed) internal {\\n require(\\n currentState(self) == State.AWAITING_SEED,\\n \\\"Current state is not AWAITING_SEED\\\"\\n );\\n\\n emit DkgStarted(seed);\\n\\n self.startBlock = block.number;\\n self.seed = seed;\\n }\\n\\n /// @notice Allows to submit a DKG result. The submitted result does not go\\n /// through a validation and before it gets accepted, it needs to\\n /// wait through the challenge period during which everyone has\\n /// a chance to challenge the result as invalid one. Submitter of\\n /// the result needs to be in the sortition pool and if the result\\n /// gets challenged, the submitter will get slashed.\\n function submitResult(Data storage self, Result calldata result) internal {\\n require(\\n currentState(self) == State.AWAITING_RESULT,\\n \\\"Current state is not AWAITING_RESULT\\\"\\n );\\n require(!hasDkgTimedOut(self), \\\"DKG timeout already passed\\\");\\n\\n SortitionPool sortitionPool = self.sortitionPool;\\n\\n // Submitter must be an operator in the sortition pool.\\n // Declared submitter's member index in the DKG result needs to match\\n // the address calling this function.\\n require(\\n sortitionPool.isOperatorInPool(msg.sender),\\n \\\"Submitter not in the sortition pool\\\"\\n );\\n require(\\n sortitionPool.getIDOperator(\\n result.members[result.submitterMemberIndex - 1]\\n ) == msg.sender,\\n \\\"Unexpected submitter index\\\"\\n );\\n\\n self.submittedResultHash = keccak256(abi.encode(result));\\n self.submittedResultBlock = block.number;\\n\\n emit DkgResultSubmitted(self.submittedResultHash, self.seed, result);\\n }\\n\\n /// @notice Checks if awaiting seed timed out.\\n /// @return True if awaiting seed timed out, false otherwise.\\n function hasSeedTimedOut(Data storage self) internal view returns (bool) {\\n return\\n currentState(self) == State.AWAITING_SEED &&\\n block.number > (self.stateLockBlock + self.parameters.seedTimeout);\\n }\\n\\n /// @notice Checks if DKG timed out. The DKG timeout period includes time required\\n /// for off-chain protocol execution and time for the result publication.\\n /// After this time a result cannot be submitted and DKG can be notified\\n /// about the timeout. DKG period is adjusted by result submission\\n /// offset that include blocks that were mined while invalid result\\n /// has been registered until it got challenged.\\n /// @return True if DKG timed out, false otherwise.\\n function hasDkgTimedOut(Data storage self) internal view returns (bool) {\\n return\\n currentState(self) == State.AWAITING_RESULT &&\\n block.number >\\n (self.startBlock +\\n self.resultSubmissionStartBlockOffset +\\n self.parameters.resultSubmissionTimeout);\\n }\\n\\n /// @notice Notifies about the seed was not delivered and restores the\\n /// initial DKG state (IDLE).\\n function notifySeedTimeout(Data storage self) internal {\\n require(hasSeedTimedOut(self), \\\"Awaiting seed has not timed out\\\");\\n\\n emit DkgSeedTimedOut();\\n\\n complete(self);\\n }\\n\\n /// @notice Notifies about DKG timeout.\\n function notifyDkgTimeout(Data storage self) internal {\\n require(hasDkgTimedOut(self), \\\"DKG has not timed out\\\");\\n\\n emit DkgTimedOut();\\n\\n complete(self);\\n }\\n\\n /// @notice Approves DKG result. Can be called when the challenge period for\\n /// the submitted result is finished. Considers the submitted result\\n /// as valid. For the first `submitterPrecedencePeriodLength`\\n /// blocks after the end of the challenge period can be called only\\n /// by the DKG result submitter. After that time, can be called by\\n /// anyone.\\n /// @dev Can be called after a challenge period for the submitted result.\\n /// @param result Result to approve. Must match the submitted result stored\\n /// during `submitResult`.\\n /// @return misbehavedMembers Identifiers of members who misbehaved during DKG.\\n function approveResult(Data storage self, Result calldata result)\\n internal\\n returns (uint32[] memory misbehavedMembers)\\n {\\n require(\\n currentState(self) == State.CHALLENGE,\\n \\\"Current state is not CHALLENGE\\\"\\n );\\n\\n uint256 challengePeriodEnd = self.submittedResultBlock +\\n self.parameters.resultChallengePeriodLength;\\n\\n require(\\n block.number > challengePeriodEnd,\\n \\\"Challenge period has not passed yet\\\"\\n );\\n\\n require(\\n keccak256(abi.encode(result)) == self.submittedResultHash,\\n \\\"Result under approval is different than the submitted one\\\"\\n );\\n\\n // Extract submitter member address. Submitter member index is in\\n // range [1, groupSize] so we need to -1 when fetching identifier from members\\n // array.\\n address submitterMember = self.sortitionPool.getIDOperator(\\n result.members[result.submitterMemberIndex - 1]\\n );\\n\\n require(\\n msg.sender == submitterMember ||\\n block.number >\\n challengePeriodEnd +\\n self.parameters.submitterPrecedencePeriodLength,\\n \\\"Only the DKG result submitter can approve the result at this moment\\\"\\n );\\n\\n // Extract misbehaved members identifiers. Misbehaved members indices\\n // are in range [1, groupSize], so we need to -1 when fetching identifiers from\\n // members array.\\n misbehavedMembers = new uint32[](\\n result.misbehavedMembersIndices.length\\n );\\n for (uint256 i = 0; i < result.misbehavedMembersIndices.length; i++) {\\n misbehavedMembers[i] = result.members[\\n result.misbehavedMembersIndices[i] - 1\\n ];\\n }\\n\\n emit DkgResultApproved(self.submittedResultHash, msg.sender);\\n\\n return misbehavedMembers;\\n }\\n\\n /// @notice Challenges DKG result. If the submitted result is proved to be\\n /// invalid it reverts the DKG back to the result submission phase.\\n /// @dev Can be called during a challenge period for the submitted result.\\n /// @param result Result to challenge. Must match the submitted result\\n /// stored during `submitResult`.\\n /// @return maliciousResultHash Hash of the malicious result.\\n /// @return maliciousSubmitter Identifier of the malicious submitter.\\n function challengeResult(Data storage self, Result calldata result)\\n internal\\n returns (bytes32 maliciousResultHash, uint32 maliciousSubmitter)\\n {\\n require(\\n currentState(self) == State.CHALLENGE,\\n \\\"Current state is not CHALLENGE\\\"\\n );\\n\\n require(\\n block.number <=\\n self.submittedResultBlock +\\n self.parameters.resultChallengePeriodLength,\\n \\\"Challenge period has already passed\\\"\\n );\\n\\n require(\\n keccak256(abi.encode(result)) == self.submittedResultHash,\\n \\\"Result under challenge is different than the submitted one\\\"\\n );\\n\\n // https://github.com/crytic/slither/issues/982\\n // slither-disable-next-line unused-return\\n try\\n self.dkgValidator.validate(result, self.seed, self.startBlock)\\n returns (\\n // slither-disable-next-line uninitialized-local,variable-scope\\n bool isValid,\\n // slither-disable-next-line uninitialized-local,variable-scope\\n string memory errorMsg\\n ) {\\n if (isValid) {\\n revert(\\\"unjustified challenge\\\");\\n }\\n\\n emit DkgResultChallenged(\\n self.submittedResultHash,\\n msg.sender,\\n errorMsg\\n );\\n } catch {\\n // if the validation reverted we consider the DKG result as invalid\\n emit DkgResultChallenged(\\n self.submittedResultHash,\\n msg.sender,\\n \\\"validation reverted\\\"\\n );\\n }\\n\\n // Consider result hash as malicious.\\n maliciousResultHash = self.submittedResultHash;\\n maliciousSubmitter = result.members[result.submitterMemberIndex - 1];\\n\\n // Adjust DKG result submission block start, so submission stage starts\\n // from the beginning.\\n self.resultSubmissionStartBlockOffset = block.number - self.startBlock;\\n\\n submittedResultCleanup(self);\\n\\n return (maliciousResultHash, maliciousSubmitter);\\n }\\n\\n /// @notice Due to EIP150, 1/64 of the gas is not forwarded to the call, and\\n /// will be kept to execute the remaining operations in the function\\n /// after the call inside the try-catch.\\n ///\\n /// To ensure there is no way for the caller to manipulate gas limit\\n /// in such a way that the call inside try-catch fails with out-of-gas\\n /// and the rest of the function is executed with the remaining\\n /// 1/64 of gas, we require an extra gas amount to be left at the\\n /// end of the call to the function challenging DKG result and\\n /// wrapping the call to EcdsaDkgValidator and TokenStaking\\n /// contracts inside a try-catch.\\n function requireChallengeExtraGas(Data storage self) internal view {\\n require(\\n gasleft() >= self.parameters.resultChallengeExtraGas,\\n \\\"Not enough extra gas left\\\"\\n );\\n }\\n\\n /// @notice Checks if DKG result is valid for the current DKG.\\n /// @param result DKG result.\\n /// @return True if the result is valid. If the result is invalid it returns\\n /// false and an error message.\\n function isResultValid(Data storage self, Result calldata result)\\n internal\\n view\\n returns (bool, string memory)\\n {\\n require(self.startBlock > 0, \\\"DKG has not been started\\\");\\n\\n return self.dkgValidator.validate(result, self.seed, self.startBlock);\\n }\\n\\n /// @notice Set setSeedTimeout parameter.\\n function setSeedTimeout(Data storage self, uint256 newSeedTimeout)\\n internal\\n {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(newSeedTimeout > 0, \\\"New value should be greater than zero\\\");\\n\\n self.parameters.seedTimeout = newSeedTimeout;\\n }\\n\\n /// @notice Set resultChallengePeriodLength parameter.\\n function setResultChallengePeriodLength(\\n Data storage self,\\n uint256 newResultChallengePeriodLength\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(\\n newResultChallengePeriodLength > 0,\\n \\\"New value should be greater than zero\\\"\\n );\\n\\n self\\n .parameters\\n .resultChallengePeriodLength = newResultChallengePeriodLength;\\n }\\n\\n /// @notice Set resultChallengeExtraGas parameter.\\n function setResultChallengeExtraGas(\\n Data storage self,\\n uint256 newResultChallengeExtraGas\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n self.parameters.resultChallengeExtraGas = newResultChallengeExtraGas;\\n }\\n\\n /// @notice Set resultSubmissionTimeout parameter.\\n function setResultSubmissionTimeout(\\n Data storage self,\\n uint256 newResultSubmissionTimeout\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(\\n newResultSubmissionTimeout > 0,\\n \\\"New value should be greater than zero\\\"\\n );\\n\\n self.parameters.resultSubmissionTimeout = newResultSubmissionTimeout;\\n }\\n\\n /// @notice Set submitterPrecedencePeriodLength parameter.\\n function setSubmitterPrecedencePeriodLength(\\n Data storage self,\\n uint256 newSubmitterPrecedencePeriodLength\\n ) internal {\\n require(currentState(self) == State.IDLE, \\\"Current state is not IDLE\\\");\\n\\n require(\\n newSubmitterPrecedencePeriodLength <\\n self.parameters.resultSubmissionTimeout,\\n \\\"New value should be less than result submission period length\\\"\\n );\\n\\n self\\n .parameters\\n .submitterPrecedencePeriodLength = newSubmitterPrecedencePeriodLength;\\n }\\n\\n /// @notice Completes DKG by cleaning up state.\\n /// @dev Should be called after DKG times out or a result is approved.\\n function complete(Data storage self) internal {\\n delete self.startBlock;\\n delete self.seed;\\n delete self.resultSubmissionStartBlockOffset;\\n submittedResultCleanup(self);\\n self.sortitionPool.unlock();\\n }\\n\\n /// @notice Cleans up submitted result state either after DKG completion\\n /// (as part of `complete` method) or after justified challenge.\\n function submittedResultCleanup(Data storage self) private {\\n delete self.submittedResultHash;\\n delete self.submittedResultBlock;\\n }\\n}\\n\",\"keccak256\":\"0xd6c442e1db2dc95730443fb6496d47889a69bed2fbace466b27f9727484c25ec\",\"license\":\"GPL-3.0-only\"},\"@keep-network/random-beacon/contracts/Governable.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\n\\n/// @notice Governable contract.\\n/// @dev A constructor is not defined, which makes the contract compatible with\\n/// upgradable proxies. This requires calling explicitly `_transferGovernance`\\n/// function in a child contract.\\nabstract contract Governable {\\n // Governance of the contract\\n // The variable should be initialized by the implementing contract.\\n // slither-disable-next-line uninitialized-state\\n address public governance;\\n\\n // Reserved storage space in case we need to add more variables,\\n // since there are upgradeable contracts that inherit from this one.\\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n // slither-disable-next-line unused-state\\n uint256[49] private __gap;\\n\\n event GovernanceTransferred(address oldGovernance, address newGovernance);\\n\\n modifier onlyGovernance() virtual {\\n require(governance == msg.sender, \\\"Caller is not the governance\\\");\\n _;\\n }\\n\\n /// @notice Transfers governance of the contract to `newGovernance`.\\n function transferGovernance(address newGovernance)\\n external\\n virtual\\n onlyGovernance\\n {\\n require(\\n newGovernance != address(0),\\n \\\"New governance is the zero address\\\"\\n );\\n _transferGovernance(newGovernance);\\n }\\n\\n function _transferGovernance(address newGovernance) internal virtual {\\n address oldGovernance = governance;\\n governance = newGovernance;\\n emit GovernanceTransferred(oldGovernance, newGovernance);\\n }\\n}\\n\",\"keccak256\":\"0xcc6a0fe8fdf05a805d2874dc7dd76dede1eb16e3ab77f2d0069dbb92272ab0a3\",\"license\":\"GPL-3.0-only\"},\"@keep-network/random-beacon/contracts/ReimbursementPool.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n// Trust math, not hardware.\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/security/ReentrancyGuard.sol\\\";\\n\\ncontract ReimbursementPool is Ownable, ReentrancyGuard {\\n /// @notice Authorized contracts that can interact with the reimbursment pool.\\n /// Authorization can be granted and removed by the owner.\\n mapping(address => bool) public isAuthorized;\\n\\n /// @notice Static gas includes:\\n /// - cost of the refund function\\n /// - base transaction cost\\n uint256 public staticGas;\\n\\n /// @notice Max gas price used to reimburse a transaction submitter. Protects\\n /// against malicious operator-miners.\\n uint256 public maxGasPrice;\\n\\n event StaticGasUpdated(uint256 newStaticGas);\\n\\n event MaxGasPriceUpdated(uint256 newMaxGasPrice);\\n\\n event SendingEtherFailed(uint256 refundAmount, address receiver);\\n\\n event AuthorizedContract(address thirdPartyContract);\\n\\n event UnauthorizedContract(address thirdPartyContract);\\n\\n event FundsWithdrawn(uint256 withdrawnAmount, address receiver);\\n\\n constructor(uint256 _staticGas, uint256 _maxGasPrice) {\\n staticGas = _staticGas;\\n maxGasPrice = _maxGasPrice;\\n }\\n\\n /// @notice Receive ETH\\n receive() external payable {}\\n\\n /// @notice Refunds ETH to a spender for executing specific transactions.\\n /// @dev Ignoring the result of sending ETH to a receiver is made on purpose.\\n /// For EOA receiving ETH should always work. If a receiver is a smart\\n /// contract, then we do not want to fail a transaction, because in some\\n /// cases the refund is done at the very end of multiple calls where all\\n /// the previous calls were already paid off. It is a receiver's smart\\n /// contract resposibility to make sure it can receive ETH.\\n /// @dev Only authorized contracts are allowed calling this function.\\n /// @param gasSpent Gas spent on a transaction that needs to be reimbursed.\\n /// @param receiver Address where the reimbursment is sent.\\n function refund(uint256 gasSpent, address receiver) external nonReentrant {\\n require(\\n isAuthorized[msg.sender],\\n \\\"Contract is not authorized for a refund\\\"\\n );\\n require(receiver != address(0), \\\"Receiver's address cannot be zero\\\");\\n\\n uint256 gasPrice = tx.gasprice < maxGasPrice\\n ? tx.gasprice\\n : maxGasPrice;\\n\\n uint256 refundAmount = (gasSpent + staticGas) * gasPrice;\\n\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,unchecked-lowlevel\\n (bool sent, ) = receiver.call{value: refundAmount}(\\\"\\\");\\n /* solhint-enable avoid-low-level-calls */\\n if (!sent) {\\n // slither-disable-next-line reentrancy-events\\n emit SendingEtherFailed(refundAmount, receiver);\\n }\\n }\\n\\n /// @notice Authorize a contract that can interact with this reimbursment pool.\\n /// Can be authorized by the owner only.\\n /// @param _contract Authorized contract.\\n function authorize(address _contract) external onlyOwner {\\n isAuthorized[_contract] = true;\\n\\n emit AuthorizedContract(_contract);\\n }\\n\\n /// @notice Unauthorize a contract that was previously authorized to interact\\n /// with this reimbursment pool. Can be unauthorized by the\\n /// owner only.\\n /// @param _contract Authorized contract.\\n function unauthorize(address _contract) external onlyOwner {\\n delete isAuthorized[_contract];\\n\\n emit UnauthorizedContract(_contract);\\n }\\n\\n /// @notice Setting a static gas cost for executing a transaction. Can be set\\n /// by the owner only.\\n /// @param _staticGas Static gas cost.\\n function setStaticGas(uint256 _staticGas) external onlyOwner {\\n staticGas = _staticGas;\\n\\n emit StaticGasUpdated(_staticGas);\\n }\\n\\n /// @notice Setting a max gas price for transactions. Can be set by the\\n /// owner only.\\n /// @param _maxGasPrice Max gas price used to reimburse tx submitters.\\n function setMaxGasPrice(uint256 _maxGasPrice) external onlyOwner {\\n maxGasPrice = _maxGasPrice;\\n\\n emit MaxGasPriceUpdated(_maxGasPrice);\\n }\\n\\n /// @notice Withdraws all ETH from this pool which are sent to a given\\n /// address. Can be set by the owner only.\\n /// @param receiver An address where ETH is sent.\\n function withdrawAll(address receiver) external onlyOwner {\\n withdraw(address(this).balance, receiver);\\n }\\n\\n /// @notice Withdraws ETH amount from this pool which are sent to a given\\n /// address. Can be set by the owner only.\\n /// @param amount Amount to withdraw from the pool.\\n /// @param receiver An address where ETH is sent.\\n function withdraw(uint256 amount, address receiver) public onlyOwner {\\n require(\\n address(this).balance >= amount,\\n \\\"Insufficient contract balance\\\"\\n );\\n require(receiver != address(0), \\\"Receiver's address cannot be zero\\\");\\n\\n emit FundsWithdrawn(amount, receiver);\\n\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,arbitrary-send\\n (bool sent, ) = receiver.call{value: amount}(\\\"\\\");\\n /* solhint-enable avoid-low-level-calls */\\n require(sent, \\\"Failed to send Ether\\\");\\n }\\n}\\n\",\"keccak256\":\"0xd6c24368cc4c6349b8b614e878ca961cad8254b8e8db1cc0abe452a70022ce50\",\"license\":\"GPL-3.0-only\"},\"@keep-network/random-beacon/contracts/libraries/BytesLib.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n//\\n// \\u2593\\u2593\\u258c \\u2593\\u2593 \\u2590\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584\\u2584\\u2584\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\u2580\\u2580\\u2580 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2580\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2580\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2584 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u258c\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2588\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n// \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2590\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593 \\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\u2593\\n//\\n//\\n\\npragma solidity 0.8.17;\\n\\n/*\\nVersion pulled from keep-core v1:\\nhttps://github.com/keep-network/keep-core/blob/f297202db00c027978ad8e7103a356503de5773c/solidity-v1/contracts/utils/BytesLib.sol\\n\\nTo compile it with solidity 0.8 `_preBytes_slot` was replaced with `_preBytes.slot`.\\n*/\\n\\n/*\\nhttps://github.com/GNSPS/solidity-bytes-utils/\\nThis is free and unencumbered software released into the public domain.\\nAnyone is free to copy, modify, publish, use, compile, sell, or\\ndistribute this software, either in source code form or as a compiled\\nbinary, for any purpose, commercial or non-commercial, and by any\\nmeans.\\nIn jurisdictions that recognize copyright laws, the author or authors\\nof this software dedicate any and all copyright interest in the\\nsoftware to the public domain. We make this dedication for the benefit\\nof the public at large and to the detriment of our heirs and\\nsuccessors. We intend this dedication to be an overt act of\\nrelinquishment in perpetuity of all present and future rights to this\\nsoftware under copyright law.\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND,\\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\\nOTHER DEALINGS IN THE SOFTWARE.\\nFor more information, please refer to \\n*/\\n\\n/** @title BytesLib **/\\n/** @author https://github.com/GNSPS **/\\n\\nlibrary BytesLib {\\n function concatStorage(bytes storage _preBytes, bytes memory _postBytes)\\n internal\\n {\\n assembly {\\n // Read the first 32 bytes of _preBytes storage, which is the length\\n // of the array. (We don't need to use the offset into the slot\\n // because arrays use the entire slot.)\\n let fslot := sload(_preBytes.slot)\\n // Arrays of 31 bytes or less have an even value in their slot,\\n // while longer arrays have an odd value. The actual length is\\n // the slot divided by two for odd values, and the lowest order\\n // byte divided by two for even values.\\n // If the slot is even, bitwise and the slot with 255 and divide by\\n // two to get the length. If the slot is odd, bitwise and the slot\\n // with -1 and divide by two.\\n let slength := div(\\n and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),\\n 2\\n )\\n let mlength := mload(_postBytes)\\n let newlength := add(slength, mlength)\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n switch add(lt(slength, 32), lt(newlength, 32))\\n case 2 {\\n // Since the new array still fits in the slot, we just need to\\n // update the contents of the slot.\\n // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length\\n sstore(\\n _preBytes.slot,\\n // all the modifications to the slot are inside this\\n // next block\\n add(\\n // we can just add to the slot contents because the\\n // bytes we want to change are the LSBs\\n fslot,\\n add(\\n mul(\\n div(\\n // load the bytes from memory\\n mload(add(_postBytes, 0x20)),\\n // zero all bytes to the right\\n exp(0x100, sub(32, mlength))\\n ),\\n // and now shift left the number of bytes to\\n // leave space for the length in the slot\\n exp(0x100, sub(32, newlength))\\n ),\\n // increase length by the double of the memory\\n // bytes length\\n mul(mlength, 2)\\n )\\n )\\n )\\n }\\n case 1 {\\n // The stored value fits in the slot, but the combined value\\n // will exceed it.\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // The contents of the _postBytes array start 32 bytes into\\n // the structure. Our first read should obtain the `submod`\\n // bytes that can fit into the unused space in the last word\\n // of the stored array. To get this, we read 32 bytes starting\\n // from `submod`, so the data we read overlaps with the array\\n // contents by `submod` bytes. Masking the lowest-order\\n // `submod` bytes allows us to add that value directly to the\\n // stored value.\\n\\n let submod := sub(32, slength)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(\\n sc,\\n add(\\n and(\\n fslot,\\n 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00\\n ),\\n and(mload(mc), mask)\\n )\\n )\\n\\n for {\\n mc := add(mc, 0x20)\\n sc := add(sc, 1)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n default {\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n // Start copying to the last used word of the stored array.\\n let sc := add(keccak256(0x0, 0x20), div(slength, 32))\\n\\n // save new length\\n sstore(_preBytes.slot, add(mul(newlength, 2), 1))\\n\\n // Copy over the first `submod` bytes of the new data as in\\n // case 1 above.\\n let slengthmod := mod(slength, 32)\\n let submod := sub(32, slengthmod)\\n let mc := add(_postBytes, submod)\\n let end := add(_postBytes, mlength)\\n let mask := sub(exp(0x100, submod), 1)\\n\\n sstore(sc, add(sload(sc), and(mload(mc), mask)))\\n\\n for {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } lt(mc, end) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n sstore(sc, mload(mc))\\n }\\n\\n mask := exp(0x100, sub(mc, end))\\n\\n sstore(sc, mul(div(mload(mc), mask), mask))\\n }\\n }\\n }\\n\\n function equalStorage(bytes storage _preBytes, bytes memory _postBytes)\\n internal\\n view\\n returns (bool)\\n {\\n bool success = true;\\n\\n assembly {\\n // we know _preBytes_offset is 0\\n let fslot := sload(_preBytes.slot)\\n // Decode the length of the stored array like in concatStorage().\\n let slength := div(\\n and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)),\\n 2\\n )\\n let mlength := mload(_postBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(slength, mlength)\\n case 1 {\\n // slength can contain both the length and contents of the array\\n // if length < 32 bytes so let's prepare for that\\n // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage\\n if iszero(iszero(slength)) {\\n switch lt(slength, 32)\\n case 1 {\\n // blank the last byte which is the length\\n fslot := mul(div(fslot, 0x100), 0x100)\\n\\n if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {\\n // unsuccess:\\n success := 0\\n }\\n }\\n default {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n // get the keccak hash to get the contents of the array\\n mstore(0x0, _preBytes.slot)\\n let sc := keccak256(0x0, 0x20)\\n\\n let mc := add(_postBytes, 0x20)\\n let end := add(mc, mlength)\\n\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n for {\\n\\n } eq(add(lt(mc, end), cb), 2) {\\n sc := add(sc, 1)\\n mc := add(mc, 0x20)\\n } {\\n if iszero(eq(sload(sc), mload(mc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function concat(bytes memory _preBytes, bytes memory _postBytes)\\n internal\\n pure\\n returns (bytes memory)\\n {\\n bytes memory tempBytes;\\n\\n assembly {\\n // Get a location of some free memory and store it in tempBytes as\\n // Solidity does for memory variables.\\n tempBytes := mload(0x40)\\n\\n // Store the length of the first bytes array at the beginning of\\n // the memory for tempBytes.\\n let length := mload(_preBytes)\\n mstore(tempBytes, length)\\n\\n // Maintain a memory counter for the current write location in the\\n // temp bytes array by adding the 32 bytes for the array length to\\n // the starting location.\\n let mc := add(tempBytes, 0x20)\\n // Stop copying when the memory counter reaches the length of the\\n // first bytes array.\\n let end := add(mc, length)\\n\\n for {\\n // Initialize a copy counter to the start of the _preBytes data,\\n // 32 bytes into its memory.\\n let cc := add(_preBytes, 0x20)\\n } lt(mc, end) {\\n // Increase both counters by 32 bytes each iteration.\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // Write the _preBytes data into the tempBytes memory 32 bytes\\n // at a time.\\n mstore(mc, mload(cc))\\n }\\n\\n // Add the length of _postBytes to the current length of tempBytes\\n // and store it as the new length in the first 32 bytes of the\\n // tempBytes memory.\\n length := mload(_postBytes)\\n mstore(tempBytes, add(length, mload(tempBytes)))\\n\\n // Move the memory counter back from a multiple of 0x20 to the\\n // actual end of the _preBytes data.\\n mc := end\\n // Stop copying when the memory counter reaches the new combined\\n // length of the arrays.\\n end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n } lt(mc, end) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n mstore(mc, mload(cc))\\n }\\n\\n // Update the free-memory pointer by padding our last write location\\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\\n // next 32 byte block, then round down to the nearest multiple of\\n // 32. If the sum of the length of the two arrays is zero then add\\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\\n mstore(\\n 0x40,\\n and(\\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\\n not(31) // Round down to the nearest 32 bytes.\\n )\\n )\\n }\\n\\n return tempBytes;\\n }\\n\\n function slice(\\n bytes memory _bytes,\\n uint256 _start,\\n uint256 _length\\n ) internal pure returns (bytes memory res) {\\n uint256 _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n // Alloc bytes array with additional 32 bytes afterspace and assign it's size\\n res := mload(0x40)\\n mstore(0x40, add(add(res, 64), _length))\\n mstore(res, _length)\\n\\n // Compute distance between source and destination pointers\\n let diff := sub(res, add(_bytes, _start))\\n\\n for {\\n let src := add(add(_bytes, 32), _start)\\n let end := add(src, _length)\\n } lt(src, end) {\\n src := add(src, 32)\\n } {\\n mstore(add(src, diff), mload(src))\\n }\\n }\\n }\\n\\n function toAddress(bytes memory _bytes, uint256 _start)\\n internal\\n pure\\n returns (address)\\n {\\n uint256 _totalLen = _start + 20;\\n require(\\n _totalLen > _start && _bytes.length >= _totalLen,\\n \\\"Address conversion out of bounds.\\\"\\n );\\n address tempAddress;\\n\\n assembly {\\n tempAddress := div(\\n mload(add(add(_bytes, 0x20), _start)),\\n 0x1000000000000000000000000\\n )\\n }\\n\\n return tempAddress;\\n }\\n\\n function toUint8(bytes memory _bytes, uint256 _start)\\n internal\\n pure\\n returns (uint8)\\n {\\n require(\\n _bytes.length >= (_start + 1),\\n \\\"Uint8 conversion out of bounds.\\\"\\n );\\n uint8 tempUint;\\n\\n assembly {\\n tempUint := mload(add(add(_bytes, 0x1), _start))\\n }\\n\\n return tempUint;\\n }\\n\\n function toUint(bytes memory _bytes, uint256 _start)\\n internal\\n pure\\n returns (uint256)\\n {\\n uint256 _totalLen = _start + 32;\\n require(\\n _totalLen > _start && _bytes.length >= _totalLen,\\n \\\"Uint conversion out of bounds.\\\"\\n );\\n uint256 tempUint;\\n\\n assembly {\\n tempUint := mload(add(add(_bytes, 0x20), _start))\\n }\\n\\n return tempUint;\\n }\\n\\n function equal(bytes memory _preBytes, bytes memory _postBytes)\\n internal\\n pure\\n returns (bool)\\n {\\n bool success = true;\\n\\n assembly {\\n let length := mload(_preBytes)\\n\\n // if lengths don't match the arrays are not equal\\n switch eq(length, mload(_postBytes))\\n case 1 {\\n // cb is a circuit breaker in the for loop since there's\\n // no said feature for inline assembly loops\\n // cb = 1 - don't breaker\\n // cb = 0 - break\\n let cb := 1\\n\\n let mc := add(_preBytes, 0x20)\\n let end := add(mc, length)\\n\\n for {\\n let cc := add(_postBytes, 0x20)\\n // the next line is the loop condition:\\n // while(uint(mc < end) + cb == 2)\\n } eq(add(lt(mc, end), cb), 2) {\\n mc := add(mc, 0x20)\\n cc := add(cc, 0x20)\\n } {\\n // if any of these checks fails then arrays are not equal\\n if iszero(eq(mload(mc), mload(cc))) {\\n // unsuccess:\\n success := 0\\n cb := 0\\n }\\n }\\n }\\n default {\\n // unsuccess:\\n success := 0\\n }\\n }\\n\\n return success;\\n }\\n\\n function toBytes32(bytes memory _source)\\n internal\\n pure\\n returns (bytes32 result)\\n {\\n if (_source.length == 0) {\\n return 0x0;\\n }\\n\\n assembly {\\n result := mload(add(_source, 32))\\n }\\n }\\n\\n function keccak256Slice(\\n bytes memory _bytes,\\n uint256 _start,\\n uint256 _length\\n ) internal pure returns (bytes32 result) {\\n uint256 _end = _start + _length;\\n require(_end > _start && _bytes.length >= _end, \\\"Slice out of bounds\\\");\\n\\n assembly {\\n result := keccak256(add(add(_bytes, 32), _start), _length)\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3b76e2fe36eb777440250dcf2ea7a689375e8af22f3cc33521095ff6954becdb\",\"license\":\"GPL-3.0-only\"},\"@keep-network/sortition-pools/contracts/Branch.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Constants.sol\\\";\\n\\n/// @notice The implicit 8-ary trees of the sortition pool\\n/// rely on packing 8 \\\"slots\\\" of 32-bit values into each uint256.\\n/// The Branch library permits efficient calculations on these slots.\\nlibrary Branch {\\n /// @notice Calculate the right shift required\\n /// to make the 32 least significant bits of an uint256\\n /// be the bits of the `position`th slot\\n /// when treating the uint256 as a uint32[8].\\n ///\\n /// @dev Not used for efficiency reasons,\\n /// but left to illustrate the meaning of a common pattern.\\n /// I wish solidity had macros, even C macros.\\n function slotShift(uint256 position) internal pure returns (uint256) {\\n unchecked {\\n return position * Constants.SLOT_WIDTH;\\n }\\n }\\n\\n /// @notice Return the `position`th slot of the `node`,\\n /// treating `node` as a uint32[32].\\n function getSlot(uint256 node, uint256 position)\\n internal\\n pure\\n returns (uint256)\\n {\\n unchecked {\\n uint256 shiftBits = position * Constants.SLOT_WIDTH;\\n // Doing a bitwise AND with `SLOT_MAX`\\n // clears all but the 32 least significant bits.\\n // Because of the right shift by `slotShift(position)` bits,\\n // those 32 bits contain the 32 bits in the `position`th slot of `node`.\\n return (node >> shiftBits) & Constants.SLOT_MAX;\\n }\\n }\\n\\n /// @notice Return `node` with the `position`th slot set to zero.\\n function clearSlot(uint256 node, uint256 position)\\n internal\\n pure\\n returns (uint256)\\n {\\n unchecked {\\n uint256 shiftBits = position * Constants.SLOT_WIDTH;\\n // Shifting `SLOT_MAX` left by `slotShift(position)` bits\\n // gives us a number where all bits of the `position`th slot are set,\\n // and all other bits are unset.\\n //\\n // Using a bitwise NOT on this number,\\n // we get a uint256 where all bits are set\\n // except for those of the `position`th slot.\\n //\\n // Bitwise ANDing the original `node` with this number\\n // sets the bits of `position`th slot to zero,\\n // leaving all other bits unchanged.\\n return node & ~(Constants.SLOT_MAX << shiftBits);\\n }\\n }\\n\\n /// @notice Return `node` with the `position`th slot set to `weight`.\\n ///\\n /// @param weight The weight of of the node.\\n /// Safely truncated to a 32-bit number,\\n /// but this should never be called with an overflowing weight regardless.\\n function setSlot(\\n uint256 node,\\n uint256 position,\\n uint256 weight\\n ) internal pure returns (uint256) {\\n unchecked {\\n uint256 shiftBits = position * Constants.SLOT_WIDTH;\\n // Clear the `position`th slot like in `clearSlot()`.\\n uint256 clearedNode = node & ~(Constants.SLOT_MAX << shiftBits);\\n // Bitwise AND `weight` with `SLOT_MAX`\\n // to clear all but the 32 least significant bits.\\n //\\n // Shift this left by `slotShift(position)` bits\\n // to obtain a uint256 with all bits unset\\n // except in the `position`th slot\\n // which contains the 32-bit value of `weight`.\\n uint256 shiftedWeight = (weight & Constants.SLOT_MAX) << shiftBits;\\n // When we bitwise OR these together,\\n // all other slots except the `position`th one come from the left argument,\\n // and the `position`th gets filled with `weight` from the right argument.\\n return clearedNode | shiftedWeight;\\n }\\n }\\n\\n /// @notice Calculate the summed weight of all slots in the `node`.\\n function sumWeight(uint256 node) internal pure returns (uint256 sum) {\\n unchecked {\\n sum = node & Constants.SLOT_MAX;\\n // Iterate through each slot\\n // by shifting `node` right in increments of 32 bits,\\n // and adding the 32 least significant bits to the `sum`.\\n uint256 newNode = node >> Constants.SLOT_WIDTH;\\n while (newNode > 0) {\\n sum += (newNode & Constants.SLOT_MAX);\\n newNode = newNode >> Constants.SLOT_WIDTH;\\n }\\n return sum;\\n }\\n }\\n\\n /// @notice Pick a slot in `node` that corresponds to `index`.\\n /// Treats the node like an array of virtual stakers,\\n /// the number of virtual stakers in each slot corresponding to its weight,\\n /// and picks which slot contains the `index`th virtual staker.\\n ///\\n /// @dev Requires that `index` be lower than `sumWeight(node)`.\\n /// However, this is not enforced for performance reasons.\\n /// If `index` exceeds the permitted range,\\n /// `pickWeightedSlot()` returns the rightmost slot\\n /// and an excessively high `newIndex`.\\n ///\\n /// @return slot The slot of `node` containing the `index`th virtual staker.\\n ///\\n /// @return newIndex The index of the `index`th virtual staker of `node`\\n /// within the returned slot.\\n function pickWeightedSlot(uint256 node, uint256 index)\\n internal\\n pure\\n returns (uint256 slot, uint256 newIndex)\\n {\\n unchecked {\\n newIndex = index;\\n uint256 newNode = node;\\n uint256 currentSlotWeight = newNode & Constants.SLOT_MAX;\\n while (newIndex >= currentSlotWeight) {\\n newIndex -= currentSlotWeight;\\n slot++;\\n newNode = newNode >> Constants.SLOT_WIDTH;\\n currentSlotWeight = newNode & Constants.SLOT_MAX;\\n }\\n return (slot, newIndex);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xa7fb1c9c9789f30493e9a40e24a24f46875dc5e7630b4f67478167759f6d1882\"},\"@keep-network/sortition-pools/contracts/Chaosnet.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\n/// @title Chaosnet\\n/// @notice This is a beta staker program for stakers willing to go the extra\\n/// mile with monitoring, share their logs with the dev team, and allow to more\\n/// carefully monitor the bootstrapping network. As the network matures, the\\n/// beta program will be ended.\\ncontract Chaosnet {\\n /// @notice Indicates if the chaosnet is active. The chaosnet is active\\n /// after the contract deployment and can be ended with a call to\\n /// `deactivateChaosnet()`. Once deactivated chaosnet can not be activated\\n /// again.\\n bool public isChaosnetActive;\\n\\n /// @notice Indicates if the given operator is a beta operator for chaosnet.\\n mapping(address => bool) public isBetaOperator;\\n\\n /// @notice Address controlling chaosnet status and beta operator addresses.\\n address public chaosnetOwner;\\n\\n event BetaOperatorsAdded(address[] operators);\\n\\n event ChaosnetOwnerRoleTransferred(\\n address oldChaosnetOwner,\\n address newChaosnetOwner\\n );\\n\\n event ChaosnetDeactivated();\\n\\n constructor() {\\n _transferChaosnetOwner(msg.sender);\\n isChaosnetActive = true;\\n }\\n\\n modifier onlyChaosnetOwner() {\\n require(msg.sender == chaosnetOwner, \\\"Not the chaosnet owner\\\");\\n _;\\n }\\n\\n modifier onlyOnChaosnet() {\\n require(isChaosnetActive, \\\"Chaosnet is not active\\\");\\n _;\\n }\\n\\n /// @notice Adds beta operator to chaosnet. Can be called only by the\\n /// chaosnet owner when the chaosnet is active. Once the operator is added\\n /// as a beta operator, it can not be removed.\\n function addBetaOperators(address[] calldata operators)\\n public\\n onlyOnChaosnet\\n onlyChaosnetOwner\\n {\\n for (uint256 i = 0; i < operators.length; i++) {\\n isBetaOperator[operators[i]] = true;\\n }\\n\\n emit BetaOperatorsAdded(operators);\\n }\\n\\n /// @notice Deactivates the chaosnet. Can be called only by the chaosnet\\n /// owner. Once deactivated chaosnet can not be activated again.\\n function deactivateChaosnet() public onlyOnChaosnet onlyChaosnetOwner {\\n isChaosnetActive = false;\\n emit ChaosnetDeactivated();\\n }\\n\\n /// @notice Transfers the chaosnet owner role to another non-zero address.\\n function transferChaosnetOwnerRole(address newChaosnetOwner)\\n public\\n onlyChaosnetOwner\\n {\\n require(\\n newChaosnetOwner != address(0),\\n \\\"New chaosnet owner must not be zero address\\\"\\n );\\n _transferChaosnetOwner(newChaosnetOwner);\\n }\\n\\n function _transferChaosnetOwner(address newChaosnetOwner) internal {\\n address oldChaosnetOwner = chaosnetOwner;\\n chaosnetOwner = newChaosnetOwner;\\n emit ChaosnetOwnerRoleTransferred(oldChaosnetOwner, newChaosnetOwner);\\n }\\n}\\n\",\"keccak256\":\"0xeaf7bdd5626f88c329793a012621039692ce1b6e1f13013997ddb13d7e3032df\"},\"@keep-network/sortition-pools/contracts/Constants.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nlibrary Constants {\\n ////////////////////////////////////////////////////////////////////////////\\n // Parameters for configuration\\n\\n // How many bits a position uses per level of the tree;\\n // each branch of the tree contains 2**SLOT_BITS slots.\\n uint256 constant SLOT_BITS = 3;\\n uint256 constant LEVELS = 7;\\n ////////////////////////////////////////////////////////////////////////////\\n\\n ////////////////////////////////////////////////////////////////////////////\\n // Derived constants, do not touch\\n uint256 constant SLOT_COUNT = 2**SLOT_BITS;\\n uint256 constant SLOT_WIDTH = 256 / SLOT_COUNT;\\n uint256 constant LAST_SLOT = SLOT_COUNT - 1;\\n uint256 constant SLOT_MAX = (2**SLOT_WIDTH) - 1;\\n uint256 constant POOL_CAPACITY = SLOT_COUNT**LEVELS;\\n\\n uint256 constant ID_WIDTH = SLOT_WIDTH;\\n uint256 constant ID_MAX = SLOT_MAX;\\n\\n uint256 constant BLOCKHEIGHT_WIDTH = 96 - ID_WIDTH;\\n uint256 constant BLOCKHEIGHT_MAX = (2**BLOCKHEIGHT_WIDTH) - 1;\\n\\n uint256 constant SLOT_POINTER_MAX = (2**SLOT_BITS) - 1;\\n uint256 constant LEAF_FLAG = 1 << 255;\\n\\n uint256 constant WEIGHT_WIDTH = 256 / SLOT_COUNT;\\n ////////////////////////////////////////////////////////////////////////////\\n}\\n\",\"keccak256\":\"0xaef690ced707935745ff1482b7bb9bd9eb77bf6a39c717465e64cf12db8a7d39\"},\"@keep-network/sortition-pools/contracts/Leaf.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Constants.sol\\\";\\n\\nlibrary Leaf {\\n function make(\\n address _operator,\\n uint256 _creationBlock,\\n uint256 _id\\n ) internal pure returns (uint256) {\\n assert(_creationBlock <= type(uint64).max);\\n assert(_id <= type(uint32).max);\\n // Converting a bytesX type into a larger type\\n // adds zero bytes on the right.\\n uint256 op = uint256(bytes32(bytes20(_operator)));\\n // Bitwise AND the id to erase\\n // all but the 32 least significant bits\\n uint256 uid = _id & Constants.ID_MAX;\\n // Erase all but the 64 least significant bits,\\n // then shift left by 32 bits to make room for the id\\n uint256 cb = (_creationBlock & Constants.BLOCKHEIGHT_MAX) <<\\n Constants.ID_WIDTH;\\n // Bitwise OR them all together to get\\n // [address operator || uint64 creationBlock || uint32 id]\\n return (op | cb | uid);\\n }\\n\\n function operator(uint256 leaf) internal pure returns (address) {\\n // Converting a bytesX type into a smaller type\\n // truncates it on the right.\\n return address(bytes20(bytes32(leaf)));\\n }\\n\\n /// @notice Return the block number the leaf was created in.\\n function creationBlock(uint256 leaf) internal pure returns (uint256) {\\n return ((leaf >> Constants.ID_WIDTH) & Constants.BLOCKHEIGHT_MAX);\\n }\\n\\n function id(uint256 leaf) internal pure returns (uint32) {\\n // Id is stored in the 32 least significant bits.\\n // Bitwise AND ensures that we only get the contents of those bits.\\n return uint32(leaf & Constants.ID_MAX);\\n }\\n}\\n\",\"keccak256\":\"0xbd107a1a43e48884885e5e966ffcbcd8fa5e89863715d717bb4006e9f89cdc2b\"},\"@keep-network/sortition-pools/contracts/Position.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Constants.sol\\\";\\n\\nlibrary Position {\\n // Return the last 3 bits of a position number,\\n // corresponding to its slot in its parent\\n function slot(uint256 a) internal pure returns (uint256) {\\n return a & Constants.SLOT_POINTER_MAX;\\n }\\n\\n // Return the parent of a position number\\n function parent(uint256 a) internal pure returns (uint256) {\\n return a >> Constants.SLOT_BITS;\\n }\\n\\n // Return the location of the child of a at the given slot\\n function child(uint256 a, uint256 s) internal pure returns (uint256) {\\n return (a << Constants.SLOT_BITS) | (s & Constants.SLOT_POINTER_MAX); // slot(s)\\n }\\n\\n // Return the uint p as a flagged position uint:\\n // the least significant 21 bits contain the position\\n // and the 22nd bit is set as a flag\\n // to distinguish the position 0x000000 from an empty field.\\n function setFlag(uint256 p) internal pure returns (uint256) {\\n return p | Constants.LEAF_FLAG;\\n }\\n\\n // Turn a flagged position into an unflagged position\\n // by removing the flag at the 22nd least significant bit.\\n //\\n // We shouldn't _actually_ need this\\n // as all position-manipulating code should ignore non-position bits anyway\\n // but it's cheap to call so might as well do it.\\n function unsetFlag(uint256 p) internal pure returns (uint256) {\\n return p & (~Constants.LEAF_FLAG);\\n }\\n}\\n\",\"keccak256\":\"0xd3a927908080ac21353a92a6bce3d69e94a5c30f6b51f16b271b6cc679f110e2\"},\"@keep-network/sortition-pools/contracts/RNG.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Leaf.sol\\\";\\nimport \\\"./Constants.sol\\\";\\n\\nlibrary RNG {\\n /// @notice Get an index in the range `[0 .. range-1]`\\n /// and the new state of the RNG,\\n /// using the provided `state` of the RNG.\\n ///\\n /// @param range The upper bound of the index, exclusive.\\n ///\\n /// @param state The previous state of the RNG.\\n /// The initial state needs to be obtained\\n /// from a trusted randomness oracle (the random beacon),\\n /// or from a chain of earlier calls to `RNG.getIndex()`\\n /// on an originally trusted seed.\\n ///\\n /// @dev Calculates the number of bits required for the desired range,\\n /// takes the least significant bits of `state`\\n /// and checks if the obtained index is within the desired range.\\n /// The original state is hashed with `keccak256` to get a new state.\\n /// If the index is outside the range,\\n /// the function retries until it gets a suitable index.\\n ///\\n /// @return index A random integer between `0` and `range - 1`, inclusive.\\n ///\\n /// @return newState The new state of the RNG.\\n /// When `getIndex()` is called one or more times,\\n /// care must be taken to always use the output `state`\\n /// of the most recent call as the input `state` of a subsequent call.\\n /// At the end of a transaction calling `RNG.getIndex()`,\\n /// the previous stored state must be overwritten with the latest output.\\n function getIndex(\\n uint256 range,\\n bytes32 state,\\n uint256 bits\\n ) internal view returns (uint256, bytes32) {\\n bool found = false;\\n uint256 index = 0;\\n bytes32 newState = state;\\n while (!found) {\\n index = truncate(bits, uint256(newState));\\n newState = keccak256(abi.encodePacked(newState, address(this)));\\n if (index < range) {\\n found = true;\\n }\\n }\\n return (index, newState);\\n }\\n\\n /// @notice Calculate how many bits are required\\n /// for an index in the range `[0 .. range-1]`.\\n ///\\n /// @param range The upper bound of the desired range, exclusive.\\n ///\\n /// @return uint The smallest number of bits\\n /// that can contain the number `range-1`.\\n function bitsRequired(uint256 range) internal pure returns (uint256) {\\n unchecked {\\n if (range == 1) {\\n return 0;\\n }\\n\\n uint256 bits = Constants.WEIGHT_WIDTH - 1;\\n\\n // Left shift by `bits`,\\n // so we have a 1 in the (bits + 1)th least significant bit\\n // and 0 in other bits.\\n // If this number is equal or greater than `range`,\\n // the range [0, range-1] fits in `bits` bits.\\n //\\n // Because we loop from high bits to low bits,\\n // we find the highest number of bits that doesn't fit the range,\\n // and return that number + 1.\\n while (1 << bits >= range) {\\n bits--;\\n }\\n\\n return bits + 1;\\n }\\n }\\n\\n /// @notice Truncate `input` to the `bits` least significant bits.\\n function truncate(uint256 bits, uint256 input)\\n internal\\n pure\\n returns (uint256)\\n {\\n unchecked {\\n return input & ((1 << bits) - 1);\\n }\\n }\\n}\\n\",\"keccak256\":\"0x67f87f589cd5123ffa32f883ea2f09b0e56258508bae82b8c655b3c27c71eb5e\"},\"@keep-network/sortition-pools/contracts/Rewards.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\n/// @title Rewards\\n/// @notice Rewards are allocated proportionally to operators\\n/// present in the pool at payout based on their weight in the pool.\\n///\\n/// To facilitate this, we use a global accumulator value\\n/// to track the total rewards one unit of weight would've earned\\n/// since the creation of the pool.\\n///\\n/// Whenever a reward is paid, the accumulator is increased\\n/// by the size of the reward divided by the total weight\\n/// of all eligible operators in the pool.\\n///\\n/// Each operator has an individual accumulator value,\\n/// set to equal the global accumulator when the operator joins the pool.\\n/// This accumulator reflects the amount of rewards\\n/// that have already been accounted for with that operator.\\n///\\n/// Whenever an operator's weight in the pool changes,\\n/// we can update the amount of rewards the operator has earned\\n/// by subtracting the operator's accumulator from the global accumulator.\\n/// This gives us the amount of rewards one unit of weight has earned\\n/// since the last time the operator's rewards have been updated.\\n/// Then we multiply that by the operator's previous (pre-change) weight\\n/// to determine how much rewards in total the operator has earned,\\n/// and add this to the operator's earned rewards.\\n/// Finally, we set the operator's accumulator to the global accumulator value.\\ncontract Rewards {\\n struct OperatorRewards {\\n // The state of the global accumulator\\n // when the operator's rewards were last updated\\n uint96 accumulated;\\n // The amount of rewards collected by the operator after the latest update.\\n // The amount the operator could withdraw may equal `available`\\n // or it may be greater, if more rewards have been paid in since then.\\n // To evaulate the most recent amount including rewards potentially paid\\n // since the last update, use `availableRewards` function.\\n uint96 available;\\n // If nonzero, the operator is ineligible for rewards\\n // and may only re-enable rewards after the specified timestamp.\\n // XXX: unsigned 32-bit integer unix seconds, will break around 2106\\n uint32 ineligibleUntil;\\n // Locally cached weight of the operator,\\n // used to reduce the cost of setting operators ineligible.\\n uint32 weight;\\n }\\n\\n // The global accumulator of how much rewards\\n // a hypothetical operator of weight 1 would have earned\\n // since the creation of the pool.\\n uint96 internal globalRewardAccumulator;\\n // If the amount of reward tokens paid in\\n // does not divide cleanly by pool weight,\\n // the difference is recorded as rounding dust\\n // and added to the next reward.\\n uint96 internal rewardRoundingDust;\\n\\n // The amount of rewards that would've been earned by ineligible operators\\n // had they not been ineligible.\\n uint96 public ineligibleEarnedRewards;\\n\\n // Ineligibility times are calculated from this offset,\\n // set at contract creation.\\n uint256 internal immutable ineligibleOffsetStart;\\n\\n mapping(uint32 => OperatorRewards) internal operatorRewards;\\n\\n constructor() {\\n // solhint-disable-next-line not-rely-on-time\\n ineligibleOffsetStart = block.timestamp;\\n }\\n\\n /// @notice Return whether the operator is eligible for rewards or not.\\n function isEligibleForRewards(uint32 operator) internal view returns (bool) {\\n return operatorRewards[operator].ineligibleUntil == 0;\\n }\\n\\n /// @notice Return the time the operator's reward eligibility can be restored.\\n function rewardsEligibilityRestorableAt(uint32 operator)\\n internal\\n view\\n returns (uint256)\\n {\\n uint32 until = operatorRewards[operator].ineligibleUntil;\\n require(until != 0, \\\"Operator already eligible\\\");\\n return (uint256(until) + ineligibleOffsetStart);\\n }\\n\\n /// @notice Return whether the operator is able to restore their eligibility\\n /// for rewards right away.\\n function canRestoreRewardEligibility(uint32 operator)\\n internal\\n view\\n returns (bool)\\n {\\n // solhint-disable-next-line not-rely-on-time\\n return rewardsEligibilityRestorableAt(operator) <= block.timestamp;\\n }\\n\\n /// @notice Internal function for updating the global state of rewards.\\n function addRewards(uint96 rewardAmount, uint32 currentPoolWeight) internal {\\n require(currentPoolWeight > 0, \\\"No recipients in pool\\\");\\n\\n uint96 totalAmount = rewardAmount + rewardRoundingDust;\\n uint96 perWeightReward = totalAmount / currentPoolWeight;\\n uint96 newRoundingDust = totalAmount % currentPoolWeight;\\n\\n globalRewardAccumulator += perWeightReward;\\n rewardRoundingDust = newRoundingDust;\\n }\\n\\n /// @notice Internal function for updating the operator's reward state.\\n function updateOperatorRewards(uint32 operator, uint32 newWeight) internal {\\n uint96 acc = globalRewardAccumulator;\\n OperatorRewards memory o = operatorRewards[operator];\\n uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);\\n if (o.ineligibleUntil == 0) {\\n // If operator is not ineligible, update their earned rewards\\n o.available += accruedRewards;\\n } else {\\n // If ineligible, put the rewards into the ineligible pot\\n ineligibleEarnedRewards += accruedRewards;\\n }\\n // In any case, update their accumulator and weight\\n o.accumulated = acc;\\n o.weight = newWeight;\\n operatorRewards[operator] = o;\\n }\\n\\n /// @notice Set the amount of withdrawable tokens to zero\\n /// and return the previous withdrawable amount.\\n /// @dev Does not update the withdrawable amount,\\n /// but should usually be accompanied by an update.\\n function withdrawOperatorRewards(uint32 operator)\\n internal\\n returns (uint96 withdrawable)\\n {\\n OperatorRewards storage o = operatorRewards[operator];\\n withdrawable = o.available;\\n o.available = 0;\\n }\\n\\n /// @notice Set the amount of ineligible-earned tokens to zero\\n /// and return the previous amount.\\n function withdrawIneligibleRewards() internal returns (uint96 withdrawable) {\\n withdrawable = ineligibleEarnedRewards;\\n ineligibleEarnedRewards = 0;\\n }\\n\\n /// @notice Set the given operators as ineligible for rewards.\\n /// The operators can restore their eligibility at the given time.\\n function setIneligible(uint32[] memory operators, uint256 until) internal {\\n OperatorRewards memory o = OperatorRewards(0, 0, 0, 0);\\n uint96 globalAcc = globalRewardAccumulator;\\n uint96 accrued = 0;\\n // Record ineligibility as seconds after contract creation\\n uint32 _until = uint32(until - ineligibleOffsetStart);\\n\\n for (uint256 i = 0; i < operators.length; i++) {\\n uint32 operator = operators[i];\\n OperatorRewards storage r = operatorRewards[operator];\\n o.available = r.available;\\n o.accumulated = r.accumulated;\\n o.ineligibleUntil = r.ineligibleUntil;\\n o.weight = r.weight;\\n\\n if (o.ineligibleUntil != 0) {\\n // If operator is already ineligible,\\n // don't earn rewards or shorten its ineligibility\\n if (o.ineligibleUntil < _until) {\\n o.ineligibleUntil = _until;\\n }\\n } else {\\n // The operator becomes ineligible -> earn rewards\\n o.ineligibleUntil = _until;\\n accrued = (globalAcc - o.accumulated) * uint96(o.weight);\\n o.available += accrued;\\n }\\n o.accumulated = globalAcc;\\n\\n r.available = o.available;\\n r.accumulated = o.accumulated;\\n r.ineligibleUntil = o.ineligibleUntil;\\n r.weight = o.weight;\\n }\\n }\\n\\n /// @notice Restore the given operator's eligibility for rewards.\\n function restoreEligibility(uint32 operator) internal {\\n // solhint-disable-next-line not-rely-on-time\\n require(canRestoreRewardEligibility(operator), \\\"Operator still ineligible\\\");\\n uint96 acc = globalRewardAccumulator;\\n OperatorRewards memory o = operatorRewards[operator];\\n uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);\\n ineligibleEarnedRewards += accruedRewards;\\n o.accumulated = acc;\\n o.ineligibleUntil = 0;\\n operatorRewards[operator] = o;\\n }\\n\\n /// @notice Returns the amount of rewards currently available for withdrawal\\n /// for the given operator.\\n function availableRewards(uint32 operator) internal view returns (uint96) {\\n uint96 acc = globalRewardAccumulator;\\n OperatorRewards memory o = operatorRewards[operator];\\n if (o.ineligibleUntil == 0) {\\n // If operator is not ineligible, calculate newly accrued rewards and add\\n // them to the available ones, calculated during the last update.\\n uint96 accruedRewards = (acc - o.accumulated) * uint96(o.weight);\\n return o.available + accruedRewards;\\n } else {\\n // If ineligible, return only the rewards calculated during the last\\n // update.\\n return o.available;\\n }\\n }\\n}\\n\",\"keccak256\":\"0x3a10abb408b44335a092387b2c7ee01db3b27997f1f2c888d9b7a2d92934c4e2\"},\"@keep-network/sortition-pools/contracts/SortitionPool.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\\\";\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./RNG.sol\\\";\\nimport \\\"./SortitionTree.sol\\\";\\nimport \\\"./Rewards.sol\\\";\\nimport \\\"./Chaosnet.sol\\\";\\n\\n/// @title Sortition Pool\\n/// @notice A logarithmic data structure used to store the pool of eligible\\n/// operators weighted by their stakes. It allows to select a group of operators\\n/// based on the provided pseudo-random seed.\\ncontract SortitionPool is\\n SortitionTree,\\n Rewards,\\n Ownable,\\n Chaosnet,\\n IReceiveApproval\\n{\\n using Branch for uint256;\\n using Leaf for uint256;\\n using Position for uint256;\\n\\n IERC20WithPermit public immutable rewardToken;\\n\\n uint256 public immutable poolWeightDivisor;\\n\\n bool public isLocked;\\n\\n event IneligibleForRewards(uint32[] ids, uint256 until);\\n\\n event RewardEligibilityRestored(address indexed operator, uint32 indexed id);\\n\\n /// @notice Reverts if called while pool is locked.\\n modifier onlyUnlocked() {\\n require(!isLocked, \\\"Sortition pool locked\\\");\\n _;\\n }\\n\\n /// @notice Reverts if called while pool is unlocked.\\n modifier onlyLocked() {\\n require(isLocked, \\\"Sortition pool unlocked\\\");\\n _;\\n }\\n\\n constructor(IERC20WithPermit _rewardToken, uint256 _poolWeightDivisor) {\\n rewardToken = _rewardToken;\\n poolWeightDivisor = _poolWeightDivisor;\\n }\\n\\n function receiveApproval(\\n address sender,\\n uint256 amount,\\n address token,\\n bytes calldata\\n ) external override {\\n require(token == address(rewardToken), \\\"Unsupported token\\\");\\n rewardToken.transferFrom(sender, address(this), amount);\\n Rewards.addRewards(uint96(amount), uint32(root.sumWeight()));\\n }\\n\\n /// @notice Withdraws all available rewards for the given operator to the\\n /// given beneficiary.\\n /// @dev Can be called only be the owner. Does not validate if the provided\\n /// beneficiary is associated with the provided operator - this needs to\\n /// be done by the owner calling this function.\\n /// @return The amount of rewards withdrawn in this call.\\n function withdrawRewards(address operator, address beneficiary)\\n public\\n onlyOwner\\n returns (uint96)\\n {\\n uint32 id = getOperatorID(operator);\\n Rewards.updateOperatorRewards(id, uint32(getPoolWeight(operator)));\\n uint96 earned = Rewards.withdrawOperatorRewards(id);\\n rewardToken.transfer(beneficiary, uint256(earned));\\n return earned;\\n }\\n\\n /// @notice Withdraws rewards not allocated to operators marked as ineligible\\n /// to the given recipient address.\\n /// @dev Can be called only by the owner.\\n function withdrawIneligible(address recipient) public onlyOwner {\\n uint96 earned = Rewards.withdrawIneligibleRewards();\\n rewardToken.transfer(recipient, uint256(earned));\\n }\\n\\n /// @notice Locks the sortition pool. In locked state, members cannot be\\n /// inserted and removed from the pool. Members statuses cannot\\n /// be updated as well.\\n /// @dev Can be called only by the contract owner.\\n function lock() public onlyOwner {\\n isLocked = true;\\n }\\n\\n /// @notice Unlocks the sortition pool. Removes all restrictions set by\\n /// the `lock` method.\\n /// @dev Can be called only by the contract owner.\\n function unlock() public onlyOwner {\\n isLocked = false;\\n }\\n\\n /// @notice Inserts an operator to the pool. Reverts if the operator is\\n /// already present. Reverts if the operator is not eligible because of their\\n /// authorized stake. Reverts if the chaosnet is active and the operator is\\n /// not a beta operator.\\n /// @dev Can be called only by the contract owner.\\n /// @param operator Address of the inserted operator.\\n /// @param authorizedStake Inserted operator's authorized stake for the application.\\n function insertOperator(address operator, uint256 authorizedStake)\\n public\\n onlyOwner\\n onlyUnlocked\\n {\\n uint256 weight = getWeight(authorizedStake);\\n require(weight > 0, \\\"Operator not eligible\\\");\\n\\n if (isChaosnetActive) {\\n require(isBetaOperator[operator], \\\"Not beta operator for chaosnet\\\");\\n }\\n\\n _insertOperator(operator, weight);\\n uint32 id = getOperatorID(operator);\\n Rewards.updateOperatorRewards(id, uint32(weight));\\n }\\n\\n /// @notice Update the operator's weight if present and eligible,\\n /// or remove from the pool if present and ineligible.\\n /// @dev Can be called only by the contract owner.\\n /// @param operator Address of the updated operator.\\n /// @param authorizedStake Operator's authorized stake for the application.\\n function updateOperatorStatus(address operator, uint256 authorizedStake)\\n public\\n onlyOwner\\n onlyUnlocked\\n {\\n uint256 weight = getWeight(authorizedStake);\\n\\n uint32 id = getOperatorID(operator);\\n Rewards.updateOperatorRewards(id, uint32(weight));\\n\\n if (weight == 0) {\\n _removeOperator(operator);\\n } else {\\n updateOperator(operator, weight);\\n }\\n }\\n\\n /// @notice Set the given operators as ineligible for rewards.\\n /// The operators can restore their eligibility at the given time.\\n function setRewardIneligibility(uint32[] calldata operators, uint256 until)\\n public\\n onlyOwner\\n {\\n Rewards.setIneligible(operators, until);\\n emit IneligibleForRewards(operators, until);\\n }\\n\\n /// @notice Restores reward eligibility for the operator.\\n function restoreRewardEligibility(address operator) public {\\n uint32 id = getOperatorID(operator);\\n Rewards.restoreEligibility(id);\\n emit RewardEligibilityRestored(operator, id);\\n }\\n\\n /// @notice Returns whether the operator is eligible for rewards or not.\\n function isEligibleForRewards(address operator) public view returns (bool) {\\n uint32 id = getOperatorID(operator);\\n return Rewards.isEligibleForRewards(id);\\n }\\n\\n /// @notice Returns the time the operator's reward eligibility can be restored.\\n function rewardsEligibilityRestorableAt(address operator)\\n public\\n view\\n returns (uint256)\\n {\\n uint32 id = getOperatorID(operator);\\n return Rewards.rewardsEligibilityRestorableAt(id);\\n }\\n\\n /// @notice Returns whether the operator is able to restore their eligibility\\n /// for rewards right away.\\n function canRestoreRewardEligibility(address operator)\\n public\\n view\\n returns (bool)\\n {\\n uint32 id = getOperatorID(operator);\\n return Rewards.canRestoreRewardEligibility(id);\\n }\\n\\n /// @notice Returns the amount of rewards withdrawable for the given operator.\\n function getAvailableRewards(address operator) public view returns (uint96) {\\n uint32 id = getOperatorID(operator);\\n return availableRewards(id);\\n }\\n\\n /// @notice Return whether the operator is present in the pool.\\n function isOperatorInPool(address operator) public view returns (bool) {\\n return getFlaggedLeafPosition(operator) != 0;\\n }\\n\\n /// @notice Return whether the operator's weight in the pool\\n /// matches their eligible weight.\\n function isOperatorUpToDate(address operator, uint256 authorizedStake)\\n public\\n view\\n returns (bool)\\n {\\n return getWeight(authorizedStake) == getPoolWeight(operator);\\n }\\n\\n /// @notice Return the weight of the operator in the pool,\\n /// which may or may not be out of date.\\n function getPoolWeight(address operator) public view returns (uint256) {\\n uint256 flaggedPosition = getFlaggedLeafPosition(operator);\\n if (flaggedPosition == 0) {\\n return 0;\\n } else {\\n uint256 leafPosition = flaggedPosition.unsetFlag();\\n uint256 leafWeight = getLeafWeight(leafPosition);\\n return leafWeight;\\n }\\n }\\n\\n /// @notice Selects a new group of operators of the provided size based on\\n /// the provided pseudo-random seed. At least one operator has to be\\n /// registered in the pool, otherwise the function fails reverting the\\n /// transaction.\\n /// @param groupSize Size of the requested group\\n /// @param seed Pseudo-random number used to select operators to group\\n /// @return selected Members of the selected group\\n function selectGroup(uint256 groupSize, bytes32 seed)\\n public\\n view\\n onlyLocked\\n returns (uint32[] memory)\\n {\\n uint256 _root = root;\\n\\n bytes32 rngState = seed;\\n uint256 rngRange = _root.sumWeight();\\n require(rngRange > 0, \\\"Not enough operators in pool\\\");\\n uint256 currentIndex;\\n\\n uint256 bits = RNG.bitsRequired(rngRange);\\n\\n uint32[] memory selected = new uint32[](groupSize);\\n\\n for (uint256 i = 0; i < groupSize; i++) {\\n (currentIndex, rngState) = RNG.getIndex(rngRange, rngState, bits);\\n\\n uint256 leafPosition = pickWeightedLeaf(currentIndex, _root);\\n\\n uint256 leaf = leaves[leafPosition];\\n selected[i] = leaf.id();\\n }\\n return selected;\\n }\\n\\n function getWeight(uint256 authorization) internal view returns (uint256) {\\n return authorization / poolWeightDivisor;\\n }\\n}\\n\",\"keccak256\":\"0xab42e7c5b1828f42a73f699eb2dc96d4f793572a6323b8b1fbd7c5e0c065bda7\"},\"@keep-network/sortition-pools/contracts/SortitionTree.sol\":{\"content\":\"pragma solidity 0.8.17;\\n\\nimport \\\"./Branch.sol\\\";\\nimport \\\"./Position.sol\\\";\\nimport \\\"./Leaf.sol\\\";\\nimport \\\"./Constants.sol\\\";\\n\\ncontract SortitionTree {\\n using Branch for uint256;\\n using Position for uint256;\\n using Leaf for uint256;\\n\\n // implicit tree\\n // root 8\\n // level2 64\\n // level3 512\\n // level4 4k\\n // level5 32k\\n // level6 256k\\n // level7 2M\\n uint256 internal root;\\n\\n // A 2-index mapping from layer => (index (0-index) => branch). For example,\\n // to access the 6th branch in the 2nd layer (right below the root node; the\\n // first branch layer), call branches[2][5]. Mappings are used in place of\\n // arrays for efficiency. The root is the first layer, the branches occupy\\n // layers 2 through 7, and layer 8 is for the leaves. Following this\\n // convention, the first index in `branches` is `2`, and the last index is\\n // `7`.\\n mapping(uint256 => mapping(uint256 => uint256)) internal branches;\\n\\n // A 0-index mapping from index => leaf, acting as an array. For example, to\\n // access the 42nd leaf, call leaves[41].\\n mapping(uint256 => uint256) internal leaves;\\n\\n // the flagged (see setFlag() and unsetFlag() in Position.sol) positions\\n // of all operators present in the pool\\n mapping(address => uint256) internal flaggedLeafPosition;\\n\\n // the leaf after the rightmost occupied leaf of each stack\\n uint256 internal rightmostLeaf;\\n\\n // the empty leaves in each stack\\n // between 0 and the rightmost occupied leaf\\n uint256[] internal emptyLeaves;\\n\\n // Each operator has an uint32 ID number\\n // which is allocated when they first join the pool\\n // and remains unchanged even if they leave and rejoin the pool.\\n mapping(address => uint32) internal operatorID;\\n\\n // The idAddress array records the address corresponding to each ID number.\\n // The ID number 0 is initialized with a zero address and is not used.\\n address[] internal idAddress;\\n\\n constructor() {\\n root = 0;\\n rightmostLeaf = 0;\\n idAddress.push();\\n }\\n\\n /// @notice Return the ID number of the given operator address. An ID number\\n /// of 0 means the operator has not been allocated an ID number yet.\\n /// @param operator Address of the operator.\\n /// @return the ID number of the given operator address\\n function getOperatorID(address operator) public view returns (uint32) {\\n return operatorID[operator];\\n }\\n\\n /// @notice Get the operator address corresponding to the given ID number. A\\n /// zero address means the ID number has not been allocated yet.\\n /// @param id ID of the operator\\n /// @return the address of the operator\\n function getIDOperator(uint32 id) public view returns (address) {\\n return idAddress.length > id ? idAddress[id] : address(0);\\n }\\n\\n /// @notice Gets the operator addresses corresponding to the given ID\\n /// numbers. A zero address means the ID number has not been allocated yet.\\n /// This function works just like getIDOperator except that it allows to fetch\\n /// operator addresses for multiple IDs in one call.\\n /// @param ids the array of the operator ids\\n /// @return an array of the associated operator addresses\\n function getIDOperators(uint32[] calldata ids)\\n public\\n view\\n returns (address[] memory)\\n {\\n uint256 idCount = idAddress.length;\\n\\n address[] memory operators = new address[](ids.length);\\n for (uint256 i = 0; i < ids.length; i++) {\\n uint32 id = ids[i];\\n operators[i] = idCount > id ? idAddress[id] : address(0);\\n }\\n return operators;\\n }\\n\\n /// @notice Checks if operator is already registered in the pool.\\n /// @param operator the address of the operator\\n /// @return whether or not the operator is already registered in the pool\\n function isOperatorRegistered(address operator) public view returns (bool) {\\n return getFlaggedLeafPosition(operator) != 0;\\n }\\n\\n /// @notice Sum the number of operators in each trunk.\\n /// @return the number of operators in the pool\\n function operatorsInPool() public view returns (uint256) {\\n // Get the number of leaves that might be occupied;\\n // if `rightmostLeaf` equals `firstLeaf()` the tree must be empty,\\n // otherwise the difference between these numbers\\n // gives the number of leaves that may be occupied.\\n uint256 nPossiblyUsedLeaves = rightmostLeaf;\\n // Get the number of empty leaves\\n // not accounted for by the `rightmostLeaf`\\n uint256 nEmptyLeaves = emptyLeaves.length;\\n\\n return (nPossiblyUsedLeaves - nEmptyLeaves);\\n }\\n\\n /// @notice Convenience method to return the total weight of the pool\\n /// @return the total weight of the pool\\n function totalWeight() public view returns (uint256) {\\n return root.sumWeight();\\n }\\n\\n /// @notice Give the operator a new ID number.\\n /// Does not check if the operator already has an ID number.\\n /// @param operator the address of the operator\\n /// @return a new ID for that operator\\n function allocateOperatorID(address operator) internal returns (uint256) {\\n uint256 id = idAddress.length;\\n\\n require(id <= type(uint32).max, \\\"Pool capacity exceeded\\\");\\n\\n operatorID[operator] = uint32(id);\\n idAddress.push(operator);\\n return id;\\n }\\n\\n /// @notice Inserts an operator into the sortition pool\\n /// @param operator the address of an operator to insert\\n /// @param weight how much weight that operator has in the pool\\n function _insertOperator(address operator, uint256 weight) internal {\\n require(\\n !isOperatorRegistered(operator),\\n \\\"Operator is already registered in the pool\\\"\\n );\\n\\n // Fetch the operator's ID, and if they don't have one, allocate them one.\\n uint256 id = getOperatorID(operator);\\n if (id == 0) {\\n id = allocateOperatorID(operator);\\n }\\n\\n // Determine which leaf to insert them into\\n uint256 position = getEmptyLeafPosition();\\n // Record the block the operator was inserted in\\n uint256 theLeaf = Leaf.make(operator, block.number, id);\\n\\n // Update the leaf, and propagate the weight changes all the way up to the\\n // root.\\n root = setLeaf(position, theLeaf, weight, root);\\n\\n // Without position flags,\\n // the position 0x000000 would be treated as empty\\n flaggedLeafPosition[operator] = position.setFlag();\\n }\\n\\n /// @notice Remove an operator (and their weight) from the pool.\\n /// @param operator the address of the operator to remove\\n function _removeOperator(address operator) internal {\\n uint256 flaggedPosition = getFlaggedLeafPosition(operator);\\n require(flaggedPosition != 0, \\\"Operator is not registered in the pool\\\");\\n uint256 unflaggedPosition = flaggedPosition.unsetFlag();\\n\\n // Update the leaf, and propagate the weight changes all the way up to the\\n // root.\\n root = removeLeaf(unflaggedPosition, root);\\n removeLeafPositionRecord(operator);\\n }\\n\\n /// @notice Update an operator's weight in the pool.\\n /// @param operator the address of the operator to update\\n /// @param weight the new weight\\n function updateOperator(address operator, uint256 weight) internal {\\n require(\\n isOperatorRegistered(operator),\\n \\\"Operator is not registered in the pool\\\"\\n );\\n\\n uint256 flaggedPosition = getFlaggedLeafPosition(operator);\\n uint256 unflaggedPosition = flaggedPosition.unsetFlag();\\n root = updateLeaf(unflaggedPosition, weight, root);\\n }\\n\\n /// @notice Helper method to remove a leaf position record for an operator.\\n /// @param operator the address of the operator to remove the record for\\n function removeLeafPositionRecord(address operator) internal {\\n flaggedLeafPosition[operator] = 0;\\n }\\n\\n /// @notice Removes the data and weight from a particular leaf.\\n /// @param position the leaf index to remove\\n /// @param _root the root node containing the leaf\\n /// @return the updated root node\\n function removeLeaf(uint256 position, uint256 _root)\\n internal\\n returns (uint256)\\n {\\n uint256 rightmostSubOne = rightmostLeaf - 1;\\n bool isRightmost = position == rightmostSubOne;\\n\\n // Clears out the data in the leaf node, and then propagates the weight\\n // changes all the way up to the root.\\n uint256 newRoot = setLeaf(position, 0, 0, _root);\\n\\n // Infer if need to fall back on emptyLeaves yet\\n if (isRightmost) {\\n rightmostLeaf = rightmostSubOne;\\n } else {\\n emptyLeaves.push(position);\\n }\\n return newRoot;\\n }\\n\\n /// @notice Updates the tree to give a particular leaf a new weight.\\n /// @param position the index of the leaf to update\\n /// @param weight the new weight\\n /// @param _root the root node containing the leaf\\n /// @return the updated root node\\n function updateLeaf(\\n uint256 position,\\n uint256 weight,\\n uint256 _root\\n ) internal returns (uint256) {\\n if (getLeafWeight(position) != weight) {\\n return updateTree(position, weight, _root);\\n } else {\\n return _root;\\n }\\n }\\n\\n /// @notice Places a leaf into a particular position, with a given weight and\\n /// propagates that change.\\n /// @param position the index to place the leaf in\\n /// @param theLeaf the new leaf to place in the position\\n /// @param leafWeight the weight of the leaf\\n /// @param _root the root containing the new leaf\\n /// @return the updated root node\\n function setLeaf(\\n uint256 position,\\n uint256 theLeaf,\\n uint256 leafWeight,\\n uint256 _root\\n ) internal returns (uint256) {\\n // set leaf\\n leaves[position] = theLeaf;\\n\\n return (updateTree(position, leafWeight, _root));\\n }\\n\\n /// @notice Propagates a weight change at a position through the tree,\\n /// eventually returning the updated root.\\n /// @param position the index of leaf to update\\n /// @param weight the new weight of the leaf\\n /// @param _root the root node containing the leaf\\n /// @return the updated root node\\n function updateTree(\\n uint256 position,\\n uint256 weight,\\n uint256 _root\\n ) internal returns (uint256) {\\n uint256 childSlot;\\n uint256 treeNode;\\n uint256 newNode;\\n uint256 nodeWeight = weight;\\n\\n uint256 parent = position;\\n // set levels 7 to 2\\n for (uint256 level = Constants.LEVELS; level >= 2; level--) {\\n childSlot = parent.slot();\\n parent = parent.parent();\\n treeNode = branches[level][parent];\\n newNode = treeNode.setSlot(childSlot, nodeWeight);\\n branches[level][parent] = newNode;\\n nodeWeight = newNode.sumWeight();\\n }\\n\\n // set level Root\\n childSlot = parent.slot();\\n return _root.setSlot(childSlot, nodeWeight);\\n }\\n\\n /// @notice Retrieves the next available empty leaf position. Tries to fill\\n /// left to right first, ignoring leaf removals, and then fills\\n /// most-recent-removals first.\\n /// @return the position of the empty leaf\\n function getEmptyLeafPosition() internal returns (uint256) {\\n uint256 rLeaf = rightmostLeaf;\\n bool spaceOnRight = (rLeaf + 1) < Constants.POOL_CAPACITY;\\n if (spaceOnRight) {\\n rightmostLeaf = rLeaf + 1;\\n return rLeaf;\\n } else {\\n uint256 emptyLeafCount = emptyLeaves.length;\\n require(emptyLeafCount > 0, \\\"Pool is full\\\");\\n uint256 emptyLeaf = emptyLeaves[emptyLeafCount - 1];\\n emptyLeaves.pop();\\n return emptyLeaf;\\n }\\n }\\n\\n /// @notice Gets the flagged leaf position for an operator.\\n /// @param operator the address of the operator\\n /// @return the leaf position of that operator\\n function getFlaggedLeafPosition(address operator)\\n internal\\n view\\n returns (uint256)\\n {\\n return flaggedLeafPosition[operator];\\n }\\n\\n /// @notice Gets the weight of a leaf at a particular position.\\n /// @param position the index of the leaf\\n /// @return the weight of the leaf at that position\\n function getLeafWeight(uint256 position) internal view returns (uint256) {\\n uint256 slot = position.slot();\\n uint256 parent = position.parent();\\n\\n // A leaf's weight information is stored a 32-bit slot in the branch layer\\n // directly above the leaf layer. To access it, we calculate that slot and\\n // parent position, and always know the hard-coded layer index.\\n uint256 node = branches[Constants.LEVELS][parent];\\n return node.getSlot(slot);\\n }\\n\\n /// @notice Picks a leaf given a random index.\\n /// @param index a number in `[0, _root.totalWeight())` used to decide\\n /// between leaves\\n /// @param _root the root of the tree\\n function pickWeightedLeaf(uint256 index, uint256 _root)\\n internal\\n view\\n returns (uint256 leafPosition)\\n {\\n uint256 currentIndex = index;\\n uint256 currentNode = _root;\\n uint256 currentPosition = 0;\\n uint256 currentSlot;\\n\\n require(index < currentNode.sumWeight(), \\\"Index exceeds weight\\\");\\n\\n // get root slot\\n (currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);\\n\\n // get slots from levels 2 to 7\\n for (uint256 level = 2; level <= Constants.LEVELS; level++) {\\n currentPosition = currentPosition.child(currentSlot);\\n currentNode = branches[level][currentPosition];\\n (currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);\\n }\\n\\n // get leaf position\\n leafPosition = currentPosition.child(currentSlot);\\n }\\n}\\n\",\"keccak256\":\"0x51daeca62ef52be78a1a9de4d2a1c5900c873165f59eda14d5965d7d7da90a03\"},\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (proxy/utils/Initializable.sol)\\n\\npragma solidity ^0.8.2;\\n\\nimport \\\"../../utils/AddressUpgradeable.sol\\\";\\n\\n/**\\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\\n *\\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\\n * reused. This mechanism prevents re-execution of each \\\"step\\\" but allows the creation of new initialization steps in\\n * case an upgrade adds a module that needs to be initialized.\\n *\\n * For example:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * contract MyToken is ERC20Upgradeable {\\n * function initialize() initializer public {\\n * __ERC20_init(\\\"MyToken\\\", \\\"MTK\\\");\\n * }\\n * }\\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\\n * function initializeV2() reinitializer(2) public {\\n * __ERC20Permit_init(\\\"MyToken\\\");\\n * }\\n * }\\n * ```\\n *\\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\\n *\\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\\n *\\n * [CAUTION]\\n * ====\\n * Avoid leaving a contract uninitialized.\\n *\\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\\n *\\n * [.hljs-theme-light.nopadding]\\n * ```\\n * /// @custom:oz-upgrades-unsafe-allow constructor\\n * constructor() {\\n * _disableInitializers();\\n * }\\n * ```\\n * ====\\n */\\nabstract contract Initializable {\\n /**\\n * @dev Indicates that the contract has been initialized.\\n * @custom:oz-retyped-from bool\\n */\\n uint8 private _initialized;\\n\\n /**\\n * @dev Indicates that the contract is in the process of being initialized.\\n */\\n bool private _initializing;\\n\\n /**\\n * @dev Triggered when the contract has been initialized or reinitialized.\\n */\\n event Initialized(uint8 version);\\n\\n /**\\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\\n * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.\\n */\\n modifier initializer() {\\n bool isTopLevelCall = _setInitializedVersion(1);\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(1);\\n }\\n }\\n\\n /**\\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\\n * used to initialize parent contracts.\\n *\\n * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original\\n * initialization step. This is essential to configure modules that are added through upgrades and that require\\n * initialization.\\n *\\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\\n * a contract, executing them in the right order is up to the developer or operator.\\n */\\n modifier reinitializer(uint8 version) {\\n bool isTopLevelCall = _setInitializedVersion(version);\\n if (isTopLevelCall) {\\n _initializing = true;\\n }\\n _;\\n if (isTopLevelCall) {\\n _initializing = false;\\n emit Initialized(version);\\n }\\n }\\n\\n /**\\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\\n */\\n modifier onlyInitializing() {\\n require(_initializing, \\\"Initializable: contract is not initializing\\\");\\n _;\\n }\\n\\n /**\\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\\n * through proxies.\\n */\\n function _disableInitializers() internal virtual {\\n _setInitializedVersion(type(uint8).max);\\n }\\n\\n function _setInitializedVersion(uint8 version) private returns (bool) {\\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\\n // inheritance patterns, but we only do this in the context of a constructor, and for the lowest level\\n // of initializers, because in other contexts the contract may have been reentered.\\n if (_initializing) {\\n require(\\n version == 1 && !AddressUpgradeable.isContract(address(this)),\\n \\\"Initializable: contract is already initialized\\\"\\n );\\n return false;\\n } else {\\n require(_initialized < version, \\\"Initializable: contract is already initialized\\\");\\n _initialized = version;\\n return true;\\n }\\n }\\n}\\n\",\"keccak256\":\"0x7454006cccb737612b00104d2f606d728e2818b778e7e55542f063c614ce46ba\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary AddressUpgradeable {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x55cf2bd9fc76704ddcdc19834cd288b7de00fc0f298a40ea16a954ae8991db2d\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/StringsUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary StringsUpgradeable {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x398d3323c1932a5986bf36be7c57593e121e69d5db5b6574b4ee0d031443de37\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../StringsUpgradeable.sol\\\";\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSAUpgradeable {\\n enum RecoverError {\\n NoError,\\n InvalidSignature,\\n InvalidSignatureLength,\\n InvalidSignatureS,\\n InvalidSignatureV\\n }\\n\\n function _throwError(RecoverError error) private pure {\\n if (error == RecoverError.NoError) {\\n return; // no error: do nothing\\n } else if (error == RecoverError.InvalidSignature) {\\n revert(\\\"ECDSA: invalid signature\\\");\\n } else if (error == RecoverError.InvalidSignatureLength) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n } else if (error == RecoverError.InvalidSignatureS) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n } else if (error == RecoverError.InvalidSignatureV) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature` or error string. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n *\\n * Documentation for signature generation:\\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\\n // Check the signature length\\n // - case 65: r,s,v signature (standard)\\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\\n if (signature.length == 65) {\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n return tryRecover(hash, v, r, s);\\n } else if (signature.length == 64) {\\n bytes32 r;\\n bytes32 vs;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n vs := mload(add(signature, 0x40))\\n }\\n return tryRecover(hash, r, vs);\\n } else {\\n return (address(0), RecoverError.InvalidSignatureLength);\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, signature);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\\n *\\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address, RecoverError) {\\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\\n uint8 v = uint8((uint256(vs) >> 255) + 27);\\n return tryRecover(hash, v, r, s);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\\n *\\n * _Available since v4.2._\\n */\\n function recover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address, RecoverError) {\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (301): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (302): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n return (address(0), RecoverError.InvalidSignatureS);\\n }\\n if (v != 27 && v != 28) {\\n return (address(0), RecoverError.InvalidSignatureV);\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n if (signer == address(0)) {\\n return (address(0), RecoverError.InvalidSignature);\\n }\\n\\n return (signer, RecoverError.NoError);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n */\\n function recover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from `s`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n\\\", StringsUpgradeable.toString(s.length), s));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Typed Data, created from a\\n * `domainSeparator` and a `structHash`. This produces hash corresponding\\n * to the one signed with the\\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\\n * JSON-RPC method as part of EIP-712.\\n *\\n * See {recover}.\\n */\\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19\\\\x01\\\", domainSeparator, structHash));\\n }\\n}\\n\",\"keccak256\":\"0x6602a65e0277f31f45cad4c7a15b024fd182f2f0e01eaa1954103b0d57848a27\",\"license\":\"MIT\"},\"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow\\n * checks.\\n *\\n * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can\\n * easily result in undesired exploitation or bugs, since developers usually\\n * assume that overflows raise errors. `SafeCast` restores this intuition by\\n * reverting the transaction when such an operation overflows.\\n *\\n * Using this library instead of the unchecked operations eliminates an entire\\n * class of bugs, so it's recommended to use it always.\\n *\\n * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing\\n * all math on `uint256` and `int256` and then downcasting.\\n */\\nlibrary SafeCastUpgradeable {\\n /**\\n * @dev Returns the downcasted uint224 from uint256, reverting on\\n * overflow (when the input is greater than largest uint224).\\n *\\n * Counterpart to Solidity's `uint224` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 224 bits\\n */\\n function toUint224(uint256 value) internal pure returns (uint224) {\\n require(value <= type(uint224).max, \\\"SafeCast: value doesn't fit in 224 bits\\\");\\n return uint224(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint128 from uint256, reverting on\\n * overflow (when the input is greater than largest uint128).\\n *\\n * Counterpart to Solidity's `uint128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n */\\n function toUint128(uint256 value) internal pure returns (uint128) {\\n require(value <= type(uint128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return uint128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint96 from uint256, reverting on\\n * overflow (when the input is greater than largest uint96).\\n *\\n * Counterpart to Solidity's `uint96` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 96 bits\\n */\\n function toUint96(uint256 value) internal pure returns (uint96) {\\n require(value <= type(uint96).max, \\\"SafeCast: value doesn't fit in 96 bits\\\");\\n return uint96(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint64 from uint256, reverting on\\n * overflow (when the input is greater than largest uint64).\\n *\\n * Counterpart to Solidity's `uint64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n */\\n function toUint64(uint256 value) internal pure returns (uint64) {\\n require(value <= type(uint64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return uint64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint32 from uint256, reverting on\\n * overflow (when the input is greater than largest uint32).\\n *\\n * Counterpart to Solidity's `uint32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n */\\n function toUint32(uint256 value) internal pure returns (uint32) {\\n require(value <= type(uint32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return uint32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint16 from uint256, reverting on\\n * overflow (when the input is greater than largest uint16).\\n *\\n * Counterpart to Solidity's `uint16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n */\\n function toUint16(uint256 value) internal pure returns (uint16) {\\n require(value <= type(uint16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return uint16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted uint8 from uint256, reverting on\\n * overflow (when the input is greater than largest uint8).\\n *\\n * Counterpart to Solidity's `uint8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n */\\n function toUint8(uint256 value) internal pure returns (uint8) {\\n require(value <= type(uint8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return uint8(value);\\n }\\n\\n /**\\n * @dev Converts a signed int256 into an unsigned uint256.\\n *\\n * Requirements:\\n *\\n * - input must be greater than or equal to 0.\\n */\\n function toUint256(int256 value) internal pure returns (uint256) {\\n require(value >= 0, \\\"SafeCast: value must be positive\\\");\\n return uint256(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int128 from int256, reverting on\\n * overflow (when the input is less than smallest int128 or\\n * greater than largest int128).\\n *\\n * Counterpart to Solidity's `int128` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 128 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt128(int256 value) internal pure returns (int128) {\\n require(value >= type(int128).min && value <= type(int128).max, \\\"SafeCast: value doesn't fit in 128 bits\\\");\\n return int128(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int64 from int256, reverting on\\n * overflow (when the input is less than smallest int64 or\\n * greater than largest int64).\\n *\\n * Counterpart to Solidity's `int64` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 64 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt64(int256 value) internal pure returns (int64) {\\n require(value >= type(int64).min && value <= type(int64).max, \\\"SafeCast: value doesn't fit in 64 bits\\\");\\n return int64(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int32 from int256, reverting on\\n * overflow (when the input is less than smallest int32 or\\n * greater than largest int32).\\n *\\n * Counterpart to Solidity's `int32` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 32 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt32(int256 value) internal pure returns (int32) {\\n require(value >= type(int32).min && value <= type(int32).max, \\\"SafeCast: value doesn't fit in 32 bits\\\");\\n return int32(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int16 from int256, reverting on\\n * overflow (when the input is less than smallest int16 or\\n * greater than largest int16).\\n *\\n * Counterpart to Solidity's `int16` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 16 bits\\n *\\n * _Available since v3.1._\\n */\\n function toInt16(int256 value) internal pure returns (int16) {\\n require(value >= type(int16).min && value <= type(int16).max, \\\"SafeCast: value doesn't fit in 16 bits\\\");\\n return int16(value);\\n }\\n\\n /**\\n * @dev Returns the downcasted int8 from int256, reverting on\\n * overflow (when the input is less than smallest int8 or\\n * greater than largest int8).\\n *\\n * Counterpart to Solidity's `int8` operator.\\n *\\n * Requirements:\\n *\\n * - input must fit into 8 bits.\\n *\\n * _Available since v3.1._\\n */\\n function toInt8(int256 value) internal pure returns (int8) {\\n require(value >= type(int8).min && value <= type(int8).max, \\\"SafeCast: value doesn't fit in 8 bits\\\");\\n return int8(value);\\n }\\n\\n /**\\n * @dev Converts an unsigned uint256 into a signed int256.\\n *\\n * Requirements:\\n *\\n * - input must be less than or equal to maxInt256.\\n */\\n function toInt256(uint256 value) internal pure returns (int256) {\\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\\n require(value <= uint256(type(int256).max), \\\"SafeCast: value doesn't fit in an int256\\\");\\n return int256(value);\\n }\\n}\\n\",\"keccak256\":\"0xcec885ecdf113b4265ed0856972d7ff167bfeb3802604b18cbb782bf47ecc4ae\",\"license\":\"MIT\"},\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor() {\\n _transferOwnership(_msgSender());\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n _transferOwnership(address(0));\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n _transferOwnership(newOwner);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Internal function without access restriction.\\n */\\n function _transferOwnership(address newOwner) internal virtual {\\n address oldOwner = _owner;\\n _owner = newOwner;\\n emit OwnershipTransferred(oldOwner, newOwner);\\n }\\n}\\n\",\"keccak256\":\"0x24e0364e503a9bbde94c715d26573a76f14cd2a202d45f96f52134ab806b67b9\",\"license\":\"MIT\"},\"@openzeppelin/contracts/security/ReentrancyGuard.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Contract module that helps prevent reentrant calls to a function.\\n *\\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\\n * available, which can be applied to functions to make sure there are no nested\\n * (reentrant) calls to them.\\n *\\n * Note that because there is a single `nonReentrant` guard, functions marked as\\n * `nonReentrant` may not call one another. This can be worked around by making\\n * those functions `private`, and then adding `external` `nonReentrant` entry\\n * points to them.\\n *\\n * TIP: If you would like to learn more about reentrancy and alternative ways\\n * to protect against it, check out our blog post\\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\\n */\\nabstract contract ReentrancyGuard {\\n // Booleans are more expensive than uint256 or any type that takes up a full\\n // word because each write operation emits an extra SLOAD to first read the\\n // slot's contents, replace the bits taken up by the boolean, and then write\\n // back. This is the compiler's defense against contract upgrades and\\n // pointer aliasing, and it cannot be disabled.\\n\\n // The values being non-zero value makes deployment a bit more expensive,\\n // but in exchange the refund on every call to nonReentrant will be lower in\\n // amount. Since refunds are capped to a percentage of the total\\n // transaction's gas, it is best to keep them low in cases like this one, to\\n // increase the likelihood of the full refund coming into effect.\\n uint256 private constant _NOT_ENTERED = 1;\\n uint256 private constant _ENTERED = 2;\\n\\n uint256 private _status;\\n\\n constructor() {\\n _status = _NOT_ENTERED;\\n }\\n\\n /**\\n * @dev Prevents a contract from calling itself, directly or indirectly.\\n * Calling a `nonReentrant` function from another `nonReentrant`\\n * function is not supported. It is possible to prevent this from happening\\n * by making the `nonReentrant` function external, and making it call a\\n * `private` function that does the actual work.\\n */\\n modifier nonReentrant() {\\n // On the first call to nonReentrant, _notEntered will be true\\n require(_status != _ENTERED, \\\"ReentrancyGuard: reentrant call\\\");\\n\\n // Any calls to nonReentrant after this point will fail\\n _status = _ENTERED;\\n\\n _;\\n\\n // By storing the original value once again, a refund is triggered (see\\n // https://eips.ethereum.org/EIPS/eip-2200)\\n _status = _NOT_ENTERED;\\n }\\n}\\n\",\"keccak256\":\"0x0e9621f60b2faabe65549f7ed0f24e8853a45c1b7990d47e8160e523683f3935\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `to`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address to, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `from` to `to` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 amount\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x9750c6b834f7b43000631af5cc30001c5f547b3ceb3635488f140f60e897ea6b\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\n\\n/**\\n * @dev Interface for the optional metadata functions from the ERC20 standard.\\n *\\n * _Available since v4.1._\\n */\\ninterface IERC20Metadata is IERC20 {\\n /**\\n * @dev Returns the name of the token.\\n */\\n function name() external view returns (string memory);\\n\\n /**\\n * @dev Returns the symbol of the token.\\n */\\n function symbol() external view returns (string memory);\\n\\n /**\\n * @dev Returns the decimals places of the token.\\n */\\n function decimals() external view returns (uint8);\\n}\\n\",\"keccak256\":\"0x8de418a5503946cabe331f35fe242d3201a73f67f77aaeb7110acb1f30423aca\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../IERC20.sol\\\";\\nimport \\\"../../../utils/Address.sol\\\";\\n\\n/**\\n * @title SafeERC20\\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\\n * contract returns false). Tokens that return no value (and instead revert or\\n * throw on failure) are also supported, non-reverting calls are assumed to be\\n * successful.\\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\\n */\\nlibrary SafeERC20 {\\n using Address for address;\\n\\n function safeTransfer(\\n IERC20 token,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\\n }\\n\\n function safeTransferFrom(\\n IERC20 token,\\n address from,\\n address to,\\n uint256 value\\n ) internal {\\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\\n }\\n\\n /**\\n * @dev Deprecated. This function has issues similar to the ones found in\\n * {IERC20-approve}, and its usage is discouraged.\\n *\\n * Whenever possible, use {safeIncreaseAllowance} and\\n * {safeDecreaseAllowance} instead.\\n */\\n function safeApprove(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n // safeApprove should only be called when setting an initial allowance,\\n // or when resetting it to zero. To increase and decrease it, use\\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\\n require(\\n (value == 0) || (token.allowance(address(this), spender) == 0),\\n \\\"SafeERC20: approve from non-zero to non-zero allowance\\\"\\n );\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\\n }\\n\\n function safeIncreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n uint256 newAllowance = token.allowance(address(this), spender) + value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n\\n function safeDecreaseAllowance(\\n IERC20 token,\\n address spender,\\n uint256 value\\n ) internal {\\n unchecked {\\n uint256 oldAllowance = token.allowance(address(this), spender);\\n require(oldAllowance >= value, \\\"SafeERC20: decreased allowance below zero\\\");\\n uint256 newAllowance = oldAllowance - value;\\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\\n }\\n }\\n\\n /**\\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\\n * on the return value: the return value is optional (but if data is returned, it must not be false).\\n * @param token The token targeted by the call.\\n * @param data The call data (encoded using abi.encode or one of its variants).\\n */\\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\\n // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that\\n // the target address contains contract code and also asserts for success in the low-level call.\\n\\n bytes memory returndata = address(token).functionCall(data, \\\"SafeERC20: low-level call failed\\\");\\n if (returndata.length > 0) {\\n // Return data is optional\\n require(abi.decode(returndata, (bool)), \\\"SafeERC20: ERC20 operation did not succeed\\\");\\n }\\n }\\n}\\n\",\"keccak256\":\"0xc3d946432c0ddbb1f846a0d3985be71299df331b91d06732152117f62f0be2b5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC721/IERC721.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../../utils/introspection/IERC165.sol\\\";\\n\\n/**\\n * @dev Required interface of an ERC721 compliant contract.\\n */\\ninterface IERC721 is IERC165 {\\n /**\\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\\n */\\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\\n\\n /**\\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\\n */\\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\\n\\n /**\\n * @dev Returns the number of tokens in ``owner``'s account.\\n */\\n function balanceOf(address owner) external view returns (uint256 balance);\\n\\n /**\\n * @dev Returns the owner of the `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function ownerOf(uint256 tokenId) external view returns (address owner);\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId,\\n bytes calldata data\\n ) external;\\n\\n /**\\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must exist and be owned by `from`.\\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\\n *\\n * Emits a {Transfer} event.\\n */\\n function safeTransferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Transfers `tokenId` token from `from` to `to`.\\n *\\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\\n *\\n * Requirements:\\n *\\n * - `from` cannot be the zero address.\\n * - `to` cannot be the zero address.\\n * - `tokenId` token must be owned by `from`.\\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(\\n address from,\\n address to,\\n uint256 tokenId\\n ) external;\\n\\n /**\\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\\n * The approval is cleared when the token is transferred.\\n *\\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\\n *\\n * Requirements:\\n *\\n * - The caller must own the token or be an approved operator.\\n * - `tokenId` must exist.\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address to, uint256 tokenId) external;\\n\\n /**\\n * @dev Approve or remove `operator` as an operator for the caller.\\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\\n *\\n * Requirements:\\n *\\n * - The `operator` cannot be the caller.\\n *\\n * Emits an {ApprovalForAll} event.\\n */\\n function setApprovalForAll(address operator, bool _approved) external;\\n\\n /**\\n * @dev Returns the account approved for `tokenId` token.\\n *\\n * Requirements:\\n *\\n * - `tokenId` must exist.\\n */\\n function getApproved(uint256 tokenId) external view returns (address operator);\\n\\n /**\\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\\n *\\n * See {setApprovalForAll}\\n */\\n function isApprovedForAll(address owner, address operator) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x0d4de01fe5360c38b4ad2b0822a12722958428f5138a7ff47c1720eb6fa52bba\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Address.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\\n\\npragma solidity ^0.8.1;\\n\\n/**\\n * @dev Collection of functions related to the address type\\n */\\nlibrary Address {\\n /**\\n * @dev Returns true if `account` is a contract.\\n *\\n * [IMPORTANT]\\n * ====\\n * It is unsafe to assume that an address for which this function returns\\n * false is an externally-owned account (EOA) and not a contract.\\n *\\n * Among others, `isContract` will return false for the following\\n * types of addresses:\\n *\\n * - an externally-owned account\\n * - a contract in construction\\n * - an address where a contract will be created\\n * - an address where a contract lived, but was destroyed\\n * ====\\n *\\n * [IMPORTANT]\\n * ====\\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\\n *\\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\\n * constructor.\\n * ====\\n */\\n function isContract(address account) internal view returns (bool) {\\n // This method relies on extcodesize/address.code.length, which returns 0\\n // for contracts in construction, since the code is only stored at the end\\n // of the constructor execution.\\n\\n return account.code.length > 0;\\n }\\n\\n /**\\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\\n * `recipient`, forwarding all available gas and reverting on errors.\\n *\\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\\n * imposed by `transfer`, making them unable to receive funds via\\n * `transfer`. {sendValue} removes this limitation.\\n *\\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\\n *\\n * IMPORTANT: because control is transferred to `recipient`, care must be\\n * taken to not create reentrancy vulnerabilities. Consider using\\n * {ReentrancyGuard} or the\\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\\n */\\n function sendValue(address payable recipient, uint256 amount) internal {\\n require(address(this).balance >= amount, \\\"Address: insufficient balance\\\");\\n\\n (bool success, ) = recipient.call{value: amount}(\\\"\\\");\\n require(success, \\\"Address: unable to send value, recipient may have reverted\\\");\\n }\\n\\n /**\\n * @dev Performs a Solidity function call using a low level `call`. A\\n * plain `call` is an unsafe replacement for a function call: use this\\n * function instead.\\n *\\n * If `target` reverts with a revert reason, it is bubbled up by this\\n * function (like regular Solidity function calls).\\n *\\n * Returns the raw returned data. To convert to the expected return value,\\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\\n *\\n * Requirements:\\n *\\n * - `target` must be a contract.\\n * - calling `target` with `data` must not revert.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionCall(target, data, \\\"Address: low-level call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\\n * `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, 0, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but also transferring `value` wei to `target`.\\n *\\n * Requirements:\\n *\\n * - the calling contract must have an ETH balance of at least `value`.\\n * - the called Solidity function must be `payable`.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value\\n ) internal returns (bytes memory) {\\n return functionCallWithValue(target, data, value, \\\"Address: low-level call with value failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\\n * with `errorMessage` as a fallback revert reason when `target` reverts.\\n *\\n * _Available since v3.1._\\n */\\n function functionCallWithValue(\\n address target,\\n bytes memory data,\\n uint256 value,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(address(this).balance >= value, \\\"Address: insufficient balance for call\\\");\\n require(isContract(target), \\\"Address: call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.call{value: value}(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\\n return functionStaticCall(target, data, \\\"Address: low-level static call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a static call.\\n *\\n * _Available since v3.3._\\n */\\n function functionStaticCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal view returns (bytes memory) {\\n require(isContract(target), \\\"Address: static call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.staticcall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\\n return functionDelegateCall(target, data, \\\"Address: low-level delegate call failed\\\");\\n }\\n\\n /**\\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\\n * but performing a delegate call.\\n *\\n * _Available since v3.4._\\n */\\n function functionDelegateCall(\\n address target,\\n bytes memory data,\\n string memory errorMessage\\n ) internal returns (bytes memory) {\\n require(isContract(target), \\\"Address: delegate call to non-contract\\\");\\n\\n (bool success, bytes memory returndata) = target.delegatecall(data);\\n return verifyCallResult(success, returndata, errorMessage);\\n }\\n\\n /**\\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\\n * revert reason using the provided one.\\n *\\n * _Available since v4.3._\\n */\\n function verifyCallResult(\\n bool success,\\n bytes memory returndata,\\n string memory errorMessage\\n ) internal pure returns (bytes memory) {\\n if (success) {\\n return returndata;\\n } else {\\n // Look for revert reason and bubble it up if present\\n if (returndata.length > 0) {\\n // The easiest way to bubble the revert reason is using memory via assembly\\n\\n assembly {\\n let returndata_size := mload(returndata)\\n revert(add(32, returndata), returndata_size)\\n }\\n } else {\\n revert(errorMessage);\\n }\\n }\\n }\\n}\\n\",\"keccak256\":\"0x2ccf9d2313a313d41a791505f2b5abfdc62191b5d4334f7f7a82691c088a1c87\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes calldata) {\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0xe2e337e6dde9ef6b680e07338c493ebea1b5fd09b43424112868e9cc1706bca7\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _HEX_SYMBOLS = \\\"0123456789abcdef\\\";\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n // Inspired by OraclizeAPI's implementation - MIT licence\\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\\n\\n if (value == 0) {\\n return \\\"0\\\";\\n }\\n uint256 temp = value;\\n uint256 digits;\\n while (temp != 0) {\\n digits++;\\n temp /= 10;\\n }\\n bytes memory buffer = new bytes(digits);\\n while (value != 0) {\\n digits -= 1;\\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\\n value /= 10;\\n }\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n if (value == 0) {\\n return \\\"0x00\\\";\\n }\\n uint256 temp = value;\\n uint256 length = 0;\\n while (temp != 0) {\\n length++;\\n temp >>= 8;\\n }\\n return toHexString(value, length);\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n}\\n\",\"keccak256\":\"0x32c202bd28995dd20c4347b7c6467a6d3241c74c8ad3edcbb610cd9205916c45\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"../Strings.sol\\\";\\n\\n/**\\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\\n *\\n * These functions can be used to verify that a message was signed by the holder\\n * of the private keys of a given address.\\n */\\nlibrary ECDSA {\\n enum RecoverError {\\n NoError,\\n InvalidSignature,\\n InvalidSignatureLength,\\n InvalidSignatureS,\\n InvalidSignatureV\\n }\\n\\n function _throwError(RecoverError error) private pure {\\n if (error == RecoverError.NoError) {\\n return; // no error: do nothing\\n } else if (error == RecoverError.InvalidSignature) {\\n revert(\\\"ECDSA: invalid signature\\\");\\n } else if (error == RecoverError.InvalidSignatureLength) {\\n revert(\\\"ECDSA: invalid signature length\\\");\\n } else if (error == RecoverError.InvalidSignatureS) {\\n revert(\\\"ECDSA: invalid signature 's' value\\\");\\n } else if (error == RecoverError.InvalidSignatureV) {\\n revert(\\\"ECDSA: invalid signature 'v' value\\\");\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature` or error string. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n *\\n * Documentation for signature generation:\\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\\n // Check the signature length\\n // - case 65: r,s,v signature (standard)\\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\\n if (signature.length == 65) {\\n bytes32 r;\\n bytes32 s;\\n uint8 v;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n s := mload(add(signature, 0x40))\\n v := byte(0, mload(add(signature, 0x60)))\\n }\\n return tryRecover(hash, v, r, s);\\n } else if (signature.length == 64) {\\n bytes32 r;\\n bytes32 vs;\\n // ecrecover takes the signature parameters, and the only way to get them\\n // currently is to use assembly.\\n assembly {\\n r := mload(add(signature, 0x20))\\n vs := mload(add(signature, 0x40))\\n }\\n return tryRecover(hash, r, vs);\\n } else {\\n return (address(0), RecoverError.InvalidSignatureLength);\\n }\\n }\\n\\n /**\\n * @dev Returns the address that signed a hashed message (`hash`) with\\n * `signature`. This address can then be used for verification purposes.\\n *\\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\\n * this function rejects them by requiring the `s` value to be in the lower\\n * half order, and the `v` value to be either 27 or 28.\\n *\\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\\n * verification to be secure: it is possible to craft signatures that\\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\\n * this is by receiving a hash of the original message (which may otherwise\\n * be too long), and then calling {toEthSignedMessageHash} on it.\\n */\\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, signature);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\\n *\\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address, RecoverError) {\\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\\n uint8 v = uint8((uint256(vs) >> 255) + 27);\\n return tryRecover(hash, v, r, s);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\\n *\\n * _Available since v4.2._\\n */\\n function recover(\\n bytes32 hash,\\n bytes32 r,\\n bytes32 vs\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n *\\n * _Available since v4.3._\\n */\\n function tryRecover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address, RecoverError) {\\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\\n // the valid range for s in (301): 0 < s < secp256k1n \\u00f7 2 + 1, and for v in (302): v \\u2208 {27, 28}. Most\\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\\n //\\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\\n // these malleable signatures as well.\\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\\n return (address(0), RecoverError.InvalidSignatureS);\\n }\\n if (v != 27 && v != 28) {\\n return (address(0), RecoverError.InvalidSignatureV);\\n }\\n\\n // If the signature is valid (and not malleable), return the signer address\\n address signer = ecrecover(hash, v, r, s);\\n if (signer == address(0)) {\\n return (address(0), RecoverError.InvalidSignature);\\n }\\n\\n return (signer, RecoverError.NoError);\\n }\\n\\n /**\\n * @dev Overload of {ECDSA-recover} that receives the `v`,\\n * `r` and `s` signature fields separately.\\n */\\n function recover(\\n bytes32 hash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) internal pure returns (address) {\\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\\n _throwError(error);\\n return recovered;\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\\n // 32 is the length in bytes of hash,\\n // enforced by the type signature above\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", hash));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Message, created from `s`. This\\n * produces hash corresponding to the one signed with the\\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\\n * JSON-RPC method as part of EIP-191.\\n *\\n * See {recover}.\\n */\\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19Ethereum Signed Message:\\\\n\\\", Strings.toString(s.length), s));\\n }\\n\\n /**\\n * @dev Returns an Ethereum Signed Typed Data, created from a\\n * `domainSeparator` and a `structHash`. This produces hash corresponding\\n * to the one signed with the\\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\\n * JSON-RPC method as part of EIP-712.\\n *\\n * See {recover}.\\n */\\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\\n return keccak256(abi.encodePacked(\\\"\\\\x19\\\\x01\\\", domainSeparator, structHash));\\n }\\n}\\n\",\"keccak256\":\"0x3c07f43e60e099b3b157243b3152722e73b80eeb7985c2cd73712828d7f7da29\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/Math.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol)\\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.\\n return (a & b) + (a ^ b) / 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\",\"keccak256\":\"0xc995bddbca1ae19788db9f8b61e63385edd3fddf89693b612d5abd1a275974d2\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IERC20WithPermit.sol\\\";\\nimport \\\"./IReceiveApproval.sol\\\";\\n\\n/// @title ERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ncontract ERC20WithPermit is IERC20WithPermit, Ownable {\\n /// @notice The amount of tokens owned by the given account.\\n mapping(address => uint256) public override balanceOf;\\n\\n /// @notice The remaining number of tokens that spender will be\\n /// allowed to spend on behalf of owner through `transferFrom` and\\n /// `burnFrom`. This is zero by default.\\n mapping(address => mapping(address => uint256)) public override allowance;\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n mapping(address => uint256) public override nonce;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n bytes32 public constant override PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n /// @notice The amount of tokens in existence.\\n uint256 public override totalSupply;\\n\\n /// @notice The name of the token.\\n string public override name;\\n\\n /// @notice The symbol of the token.\\n string public override symbol;\\n\\n /// @notice The decimals places of the token.\\n uint8 public constant override decimals = 18;\\n\\n constructor(string memory _name, string memory _symbol) {\\n name = _name;\\n symbol = _symbol;\\n\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Moves `amount` tokens from the caller's account to `recipient`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n function transfer(address recipient, uint256 amount)\\n external\\n override\\n returns (bool)\\n {\\n _transfer(msg.sender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice Moves `amount` tokens from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @return True if the operation succeeded, reverts otherwise.\\n /// @dev Requirements:\\n /// - `spender` and `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `spender`'s tokens of at least\\n /// `amount`.\\n function transferFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external override returns (bool) {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n _approve(spender, msg.sender, currentAllowance - amount);\\n }\\n _transfer(spender, recipient, amount);\\n return true;\\n }\\n\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferFrom` and `burnFrom` will\\n /// not reduce an allowance.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external override {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonce[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approve(owner, spender, amount);\\n }\\n\\n /// @notice Creates `amount` tokens and assigns them to `account`,\\n /// increasing the total supply.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address.\\n function mint(address recipient, uint256 amount) external onlyOwner {\\n require(recipient != address(0), \\\"Mint to the zero address\\\");\\n\\n beforeTokenTransfer(address(0), recipient, amount);\\n\\n totalSupply += amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(address(0), recipient, amount);\\n }\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n /// @dev Requirements:\\n /// - the caller must have a balance of at least `amount`.\\n function burn(uint256 amount) external override {\\n _burn(msg.sender, amount);\\n }\\n\\n /// @notice Destroys `amount` of tokens from `account` using the allowance\\n /// mechanism. `amount` is then deducted from the caller's allowance\\n /// unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `account` must have a balance of at least `amount`,\\n /// - the caller must have allowance for `account`'s tokens of at least\\n /// `amount`.\\n function burnFrom(address account, uint256 amount) external override {\\n uint256 currentAllowance = allowance[account][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Burn amount exceeds allowance\\\"\\n );\\n _approve(account, msg.sender, currentAllowance - amount);\\n }\\n _burn(account, amount);\\n }\\n\\n /// @notice Calls `receiveApproval` function on spender previously approving\\n /// the spender to withdraw from the caller multiple times, up to\\n /// the `amount` amount. If this function is called again, it\\n /// overwrites the current allowance with `amount`. Reverts if the\\n /// approval reverted or if `receiveApproval` call on the spender\\n /// reverted.\\n /// @return True if both approval and `receiveApproval` calls succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external override returns (bool) {\\n if (approve(spender, amount)) {\\n IReceiveApproval(spender).receiveApproval(\\n msg.sender,\\n amount,\\n address(this),\\n extraData\\n );\\n return true;\\n }\\n return false;\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// tokens.\\n /// @return True if the operation succeeded.\\n /// @dev If the `amount` is set to `type(uint256).max` then\\n /// `transferFrom` and `burnFrom` will not reduce an allowance.\\n /// Beware that changing an allowance with this method brings the risk\\n /// that someone may use both the old and the new allowance by\\n /// unfortunate transaction ordering. One possible solution to mitigate\\n /// this race condition is to first reduce the spender's allowance to 0\\n /// and set the desired value afterwards:\\n /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n function approve(address spender, uint256 amount)\\n public\\n override\\n returns (bool)\\n {\\n _approve(msg.sender, spender, amount);\\n return true;\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view override returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n /// @dev Hook that is called before any transfer of tokens. This includes\\n /// minting and burning.\\n ///\\n /// Calling conditions:\\n /// - when `from` and `to` are both non-zero, `amount` of `from`'s tokens\\n /// will be to transferred to `to`.\\n /// - when `from` is zero, `amount` tokens will be minted for `to`.\\n /// - when `to` is zero, `amount` of ``from``'s tokens will be burned.\\n /// - `from` and `to` are never both zero.\\n // slither-disable-next-line dead-code\\n function beforeTokenTransfer(\\n address from,\\n address to,\\n uint256 amount\\n ) internal virtual {}\\n\\n function _burn(address account, uint256 amount) internal {\\n uint256 currentBalance = balanceOf[account];\\n require(currentBalance >= amount, \\\"Burn amount exceeds balance\\\");\\n\\n beforeTokenTransfer(account, address(0), amount);\\n\\n balanceOf[account] = currentBalance - amount;\\n totalSupply -= amount;\\n emit Transfer(account, address(0), amount);\\n }\\n\\n function _transfer(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Transfer from the zero address\\\");\\n require(recipient != address(0), \\\"Transfer to the zero address\\\");\\n require(recipient != address(this), \\\"Transfer to the token address\\\");\\n\\n beforeTokenTransfer(spender, recipient, amount);\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n balanceOf[spender] = spenderBalance - amount;\\n balanceOf[recipient] += amount;\\n emit Transfer(spender, recipient, amount);\\n }\\n\\n function _approve(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(owner != address(0), \\\"Approve from the zero address\\\");\\n require(spender != address(0), \\\"Approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit Approval(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(name)),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x1e1bf4ec5c9d6fe70f6f834316482aeff3f122ff4ffaa7178099e7ae71a0b16d\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IApproveAndCall.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by tokens supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IApproveAndCall {\\n /// @notice Executes `receiveApproval` function on spender as specified in\\n /// `IReceiveApproval` interface. Approves spender to withdraw from\\n /// the caller multiple times, up to the `amount`. If this\\n /// function is called again, it overwrites the current allowance\\n /// with `amount`. Reverts if the approval reverted or if\\n /// `receiveApproval` call on the spender reverted.\\n function approveAndCall(\\n address spender,\\n uint256 amount,\\n bytes memory extraData\\n ) external returns (bool);\\n}\\n\",\"keccak256\":\"0x393d18ef81a57dcc96fff4c340cc2945deaebb37b9796c322cf2bc96872c3df8\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IERC20WithPermit.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\\\";\\n\\nimport \\\"./IApproveAndCall.sol\\\";\\n\\n/// @title IERC20WithPermit\\n/// @notice Burnable ERC20 token with EIP2612 permit functionality. User can\\n/// authorize a transfer of their token with a signature conforming\\n/// EIP712 standard instead of an on-chain transaction from their\\n/// address. Anyone can submit this signature on the user's behalf by\\n/// calling the permit function, as specified in EIP2612 standard,\\n/// paying gas fees, and possibly performing other actions in the same\\n/// transaction.\\ninterface IERC20WithPermit is IERC20, IERC20Metadata, IApproveAndCall {\\n /// @notice EIP2612 approval made with secp256k1 signature.\\n /// Users can authorize a transfer of their tokens with a signature\\n /// conforming EIP712 standard, rather than an on-chain transaction\\n /// from their address. Anyone can submit this signature on the\\n /// user's behalf by calling the permit function, paying gas fees,\\n /// and possibly performing other actions in the same transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external;\\n\\n /// @notice Destroys `amount` tokens from the caller.\\n function burn(uint256 amount) external;\\n\\n /// @notice Destroys `amount` of tokens from `account`, deducting the amount\\n /// from caller's allowance.\\n function burnFrom(address account, uint256 amount) external;\\n\\n /// @notice Returns hash of EIP712 Domain struct with the token name as\\n /// a signing domain and token contract as a verifying contract.\\n /// Used to construct EIP2612 signature provided to `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() external view returns (bytes32);\\n\\n /// @notice Returns the current nonce for EIP2612 permission for the\\n /// provided token owner for a replay protection. Used to construct\\n /// EIP2612 signature provided to `permit` function.\\n function nonce(address owner) external view returns (uint256);\\n\\n /// @notice Returns EIP2612 Permit message hash. Used to construct EIP2612\\n /// signature provided to `permit` function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function PERMIT_TYPEHASH() external pure returns (bytes32);\\n}\\n\",\"keccak256\":\"0xdac9a5086c19a7128b505a7be1ab0ac1aa314f6989cb88d2417e9d7383f89fa9\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/IReceiveApproval.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\n/// @notice An interface that should be implemented by contracts supporting\\n/// `approveAndCall`/`receiveApproval` pattern.\\ninterface IReceiveApproval {\\n /// @notice Receives approval to spend tokens. Called as a result of\\n /// `approveAndCall` call on the token.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x6a30d83ad230548b1e7839737affc8489a035314209de14b89dbef7fb0f66395\",\"license\":\"MIT\"},\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC721/IERC721.sol\\\";\\n\\n/// @title MisfundRecovery\\n/// @notice Allows the owner of the token contract extending MisfundRecovery\\n/// to recover any ERC20 and ERC721 sent mistakenly to the token\\n/// contract address.\\ncontract MisfundRecovery is Ownable {\\n using SafeERC20 for IERC20;\\n\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n}\\n\",\"keccak256\":\"0xbbfea02bf20e2a6df5a497bbc05c7540a3b7c7dfb8b1feeaffef7f6b8ba65d65\",\"license\":\"MIT\"},\"contracts/GovernanceUtils.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\npragma solidity 0.8.17;\\n\\nlibrary GovernanceUtils {\\n /// @notice Reverts if the governance delay has not passed since\\n /// the change initiated time or if the change has not been\\n /// initiated.\\n /// @param changeInitiatedTimestamp The timestamp at which the change has\\n /// been initiated.\\n /// @param delay Governance delay.\\n function onlyAfterGovernanceDelay(\\n uint256 changeInitiatedTimestamp,\\n uint256 delay\\n ) internal view {\\n require(changeInitiatedTimestamp > 0, \\\"Change not initiated\\\");\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp - changeInitiatedTimestamp >= delay,\\n \\\"Governance delay has not elapsed\\\"\\n );\\n }\\n\\n /// @notice Gets the time remaining until the governable parameter update\\n /// can be committed.\\n /// @param changeInitiatedTimestamp Timestamp indicating the beginning of\\n /// the change.\\n /// @param delay Governance delay.\\n /// @return Remaining time in seconds.\\n function getRemainingGovernanceDelay(\\n uint256 changeInitiatedTimestamp,\\n uint256 delay\\n ) internal view returns (uint256) {\\n require(changeInitiatedTimestamp > 0, \\\"Change not initiated\\\");\\n /* solhint-disable-next-line not-rely-on-time */\\n uint256 elapsed = block.timestamp - changeInitiatedTimestamp;\\n if (elapsed >= delay) {\\n return 0;\\n } else {\\n return delay - elapsed;\\n }\\n }\\n}\\n\",\"keccak256\":\"0x9d16501e1b7c368ced397fd2eff0ab1bd1db8d26cc3700d0b7740942ee3c3c31\",\"license\":\"GPL-3.0-only\"},\"contracts/bank/Bank.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IReceiveBalanceApproval.sol\\\";\\nimport \\\"../vault/IVault.sol\\\";\\n\\n/// @title Bitcoin Bank\\n/// @notice Bank is a central component tracking Bitcoin balances. Balances can\\n/// be transferred between balance owners, and balance owners can\\n/// approve their balances to be spent by others. Balances in the Bank\\n/// are updated for depositors who deposited their Bitcoin into the\\n/// Bridge and only the Bridge can increase balances.\\n/// @dev Bank is a governable contract and the Governance can upgrade the Bridge\\n/// address.\\ncontract Bank is Ownable {\\n address public bridge;\\n\\n /// @notice The balance of the given account in the Bank. Zero by default.\\n mapping(address => uint256) public balanceOf;\\n\\n /// @notice The remaining amount of balance a spender will be\\n /// allowed to transfer on behalf of an owner using\\n /// `transferBalanceFrom`. Zero by default.\\n mapping(address => mapping(address => uint256)) public allowance;\\n\\n /// @notice Returns the current nonce for an EIP2612 permission for the\\n /// provided balance owner to protect against replay attacks. Used\\n /// to construct an EIP2612 signature provided to the `permit`\\n /// function.\\n mapping(address => uint256) public nonces;\\n\\n uint256 public immutable cachedChainId;\\n bytes32 public immutable cachedDomainSeparator;\\n\\n /// @notice Returns an EIP2612 Permit message hash. Used to construct\\n /// an EIP2612 signature provided to the `permit` function.\\n bytes32 public constant PERMIT_TYPEHASH =\\n keccak256(\\n \\\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\\\"\\n );\\n\\n event BalanceTransferred(\\n address indexed from,\\n address indexed to,\\n uint256 amount\\n );\\n\\n event BalanceApproved(\\n address indexed owner,\\n address indexed spender,\\n uint256 amount\\n );\\n\\n event BalanceIncreased(address indexed owner, uint256 amount);\\n\\n event BalanceDecreased(address indexed owner, uint256 amount);\\n\\n event BridgeUpdated(address newBridge);\\n\\n modifier onlyBridge() {\\n require(msg.sender == address(bridge), \\\"Caller is not the bridge\\\");\\n _;\\n }\\n\\n constructor() {\\n cachedChainId = block.chainid;\\n cachedDomainSeparator = buildDomainSeparator();\\n }\\n\\n /// @notice Allows the Governance to upgrade the Bridge address.\\n /// @dev The function does not implement any governance delay and does not\\n /// check the status of the Bridge. The Governance implementation needs\\n /// to ensure all requirements for the upgrade are satisfied before\\n /// executing this function.\\n /// Requirements:\\n /// - The new Bridge address must not be zero.\\n /// @param _bridge The new Bridge address.\\n function updateBridge(address _bridge) external onlyOwner {\\n require(_bridge != address(0), \\\"Bridge address must not be 0x0\\\");\\n bridge = _bridge;\\n emit BridgeUpdated(_bridge);\\n }\\n\\n /// @notice Moves the given `amount` of balance from the caller to\\n /// `recipient`.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - the caller must have a balance of at least `amount`.\\n /// @param recipient The recipient of the balance.\\n /// @param amount The amount of the balance transferred.\\n function transferBalance(address recipient, uint256 amount) external {\\n _transferBalance(msg.sender, recipient, amount);\\n }\\n\\n /// @notice Sets `amount` as the allowance of `spender` over the caller's\\n /// balance. Does not allow updating an existing allowance to\\n /// a value that is non-zero to avoid someone using both the old and\\n /// the new allowance by unfortunate transaction ordering. To update\\n /// an allowance to a non-zero value please set it to zero first or\\n /// use `increaseBalanceAllowance` or `decreaseBalanceAllowance` for\\n /// an atomic update.\\n /// @dev If the `amount` is set to `type(uint256).max`,\\n /// `transferBalanceFrom` will not reduce an allowance.\\n /// @param spender The address that will be allowed to spend the balance.\\n /// @param amount The amount the spender is allowed to spend.\\n function approveBalance(address spender, uint256 amount) external {\\n require(\\n amount == 0 || allowance[msg.sender][spender] == 0,\\n \\\"Non-atomic allowance change not allowed\\\"\\n );\\n _approveBalance(msg.sender, spender, amount);\\n }\\n\\n /// @notice Sets the `amount` as an allowance of a smart contract `spender`\\n /// over the caller's balance and calls the `spender` via\\n /// `receiveBalanceApproval`.\\n /// @dev If the `amount` is set to `type(uint256).max`, the potential\\n /// `transferBalanceFrom` executed in `receiveBalanceApproval` of\\n /// `spender` will not reduce an allowance. Beware that changing an\\n /// allowance with this function brings the risk that `spender` may use\\n /// both the old and the new allowance by unfortunate transaction\\n /// ordering. Please use `increaseBalanceAllowance` and\\n /// `decreaseBalanceAllowance` to eliminate the risk.\\n /// @param spender The smart contract that will be allowed to spend the\\n /// balance.\\n /// @param amount The amount the spender contract is allowed to spend.\\n /// @param extraData Extra data passed to the `spender` contract via\\n /// `receiveBalanceApproval` call.\\n function approveBalanceAndCall(\\n address spender,\\n uint256 amount,\\n bytes calldata extraData\\n ) external {\\n _approveBalance(msg.sender, spender, amount);\\n IReceiveBalanceApproval(spender).receiveBalanceApproval(\\n msg.sender,\\n amount,\\n extraData\\n );\\n }\\n\\n /// @notice Atomically increases the caller's balance allowance granted to\\n /// `spender` by the given `addedValue`.\\n /// @param spender The spender address for which the allowance is increased.\\n /// @param addedValue The amount by which the allowance is increased.\\n function increaseBalanceAllowance(address spender, uint256 addedValue)\\n external\\n {\\n _approveBalance(\\n msg.sender,\\n spender,\\n allowance[msg.sender][spender] + addedValue\\n );\\n }\\n\\n /// @notice Atomically decreases the caller's balance allowance granted to\\n /// `spender` by the given `subtractedValue`.\\n /// @dev Requirements:\\n /// - `spender` must not be the zero address,\\n /// - the current allowance for `spender` must not be lower than\\n /// the `subtractedValue`.\\n /// @param spender The spender address for which the allowance is decreased.\\n /// @param subtractedValue The amount by which the allowance is decreased.\\n function decreaseBalanceAllowance(address spender, uint256 subtractedValue)\\n external\\n {\\n uint256 currentAllowance = allowance[msg.sender][spender];\\n require(\\n currentAllowance >= subtractedValue,\\n \\\"Can not decrease balance allowance below zero\\\"\\n );\\n unchecked {\\n _approveBalance(\\n msg.sender,\\n spender,\\n currentAllowance - subtractedValue\\n );\\n }\\n }\\n\\n /// @notice Moves `amount` of balance from `spender` to `recipient` using the\\n /// allowance mechanism. `amount` is then deducted from the caller's\\n /// allowance unless the allowance was made for `type(uint256).max`.\\n /// @dev Requirements:\\n /// - `recipient` cannot be the zero address,\\n /// - `spender` must have a balance of at least `amount`,\\n /// - the caller must have an allowance for `spender`'s balance of at\\n /// least `amount`.\\n /// @param spender The address from which the balance is transferred.\\n /// @param recipient The address to which the balance is transferred.\\n /// @param amount The amount of balance that is transferred.\\n function transferBalanceFrom(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) external {\\n uint256 currentAllowance = allowance[spender][msg.sender];\\n if (currentAllowance != type(uint256).max) {\\n require(\\n currentAllowance >= amount,\\n \\\"Transfer amount exceeds allowance\\\"\\n );\\n unchecked {\\n _approveBalance(spender, msg.sender, currentAllowance - amount);\\n }\\n }\\n _transferBalance(spender, recipient, amount);\\n }\\n\\n /// @notice An EIP2612 approval made with secp256k1 signature. Users can\\n /// authorize a transfer of their balance with a signature\\n /// conforming to the EIP712 standard, rather than an on-chain\\n /// transaction from their address. Anyone can submit this signature\\n /// on the user's behalf by calling the `permit` function, paying\\n /// gas fees, and possibly performing other actions in the same\\n /// transaction.\\n /// @dev The deadline argument can be set to `type(uint256).max to create\\n /// permits that effectively never expire. If the `amount` is set\\n /// to `type(uint256).max` then `transferBalanceFrom` will not\\n /// reduce an allowance. Beware that changing an allowance with this\\n /// function brings the risk that someone may use both the old and the\\n /// new allowance by unfortunate transaction ordering. Please use\\n /// `increaseBalanceAllowance` and `decreaseBalanceAllowance` to\\n /// eliminate the risk.\\n /// @param owner The balance owner who signed the permission.\\n /// @param spender The address that will be allowed to spend the balance.\\n /// @param amount The amount the spender is allowed to spend.\\n /// @param deadline The UNIX time until which the permit is valid.\\n /// @param v V part of the permit signature.\\n /// @param r R part of the permit signature.\\n /// @param s S part of the permit signature.\\n function permit(\\n address owner,\\n address spender,\\n uint256 amount,\\n uint256 deadline,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n ) external {\\n /* solhint-disable-next-line not-rely-on-time */\\n require(deadline >= block.timestamp, \\\"Permission expired\\\");\\n\\n // Validate `s` and `v` values for a malleability concern described in EIP2.\\n // Only signatures with `s` value in the lower half of the secp256k1\\n // curve's order and `v` value of 27 or 28 are considered valid.\\n require(\\n uint256(s) <=\\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0,\\n \\\"Invalid signature 's' value\\\"\\n );\\n require(v == 27 || v == 28, \\\"Invalid signature 'v' value\\\");\\n\\n bytes32 digest = keccak256(\\n abi.encodePacked(\\n \\\"\\\\x19\\\\x01\\\",\\n DOMAIN_SEPARATOR(),\\n keccak256(\\n abi.encode(\\n PERMIT_TYPEHASH,\\n owner,\\n spender,\\n amount,\\n nonces[owner]++,\\n deadline\\n )\\n )\\n )\\n );\\n address recoveredAddress = ecrecover(digest, v, r, s);\\n require(\\n recoveredAddress != address(0) && recoveredAddress == owner,\\n \\\"Invalid signature\\\"\\n );\\n _approveBalance(owner, spender, amount);\\n }\\n\\n /// @notice Increases balances of the provided `recipients` by the provided\\n /// `amounts`. Can only be called by the Bridge.\\n /// @dev Requirements:\\n /// - length of `recipients` and `amounts` must be the same,\\n /// - none of `recipients` addresses must point to the Bank.\\n /// @param recipients Balance increase recipients.\\n /// @param amounts Amounts by which balances are increased.\\n function increaseBalances(\\n address[] calldata recipients,\\n uint256[] calldata amounts\\n ) external onlyBridge {\\n require(\\n recipients.length == amounts.length,\\n \\\"Arrays must have the same length\\\"\\n );\\n for (uint256 i = 0; i < recipients.length; i++) {\\n _increaseBalance(recipients[i], amounts[i]);\\n }\\n }\\n\\n /// @notice Increases balance of the provided `recipient` by the provided\\n /// `amount`. Can only be called by the Bridge.\\n /// @dev Requirements:\\n /// - `recipient` address must not point to the Bank.\\n /// @param recipient Balance increase recipient.\\n /// @param amount Amount by which the balance is increased.\\n function increaseBalance(address recipient, uint256 amount)\\n external\\n onlyBridge\\n {\\n _increaseBalance(recipient, amount);\\n }\\n\\n /// @notice Increases the given smart contract `vault`'s balance and\\n /// notifies the `vault` contract about it.\\n /// Can be called only by the Bridge.\\n /// @dev Requirements:\\n /// - `vault` must implement `IVault` interface,\\n /// - length of `recipients` and `amounts` must be the same.\\n /// @param vault Address of `IVault` recipient contract.\\n /// @param recipients Balance increase recipients.\\n /// @param amounts Amounts by which balances are increased.\\n function increaseBalanceAndCall(\\n address vault,\\n address[] calldata recipients,\\n uint256[] calldata amounts\\n ) external onlyBridge {\\n require(\\n recipients.length == amounts.length,\\n \\\"Arrays must have the same length\\\"\\n );\\n uint256 totalAmount = 0;\\n for (uint256 i = 0; i < amounts.length; i++) {\\n totalAmount += amounts[i];\\n }\\n _increaseBalance(vault, totalAmount);\\n IVault(vault).receiveBalanceIncrease(recipients, amounts);\\n }\\n\\n /// @notice Decreases caller's balance by the provided `amount`. There is no\\n /// way to restore the balance so do not call this function unless\\n /// you really know what you are doing!\\n /// @dev Requirements:\\n /// - The caller must have a balance of at least `amount`.\\n /// @param amount The amount by which the balance is decreased.\\n function decreaseBalance(uint256 amount) external {\\n balanceOf[msg.sender] -= amount;\\n emit BalanceDecreased(msg.sender, amount);\\n }\\n\\n /// @notice Returns hash of EIP712 Domain struct with `TBTC Bank` as\\n /// a signing domain and Bank contract as a verifying contract.\\n /// Used to construct an EIP2612 signature provided to the `permit`\\n /// function.\\n /* solhint-disable-next-line func-name-mixedcase */\\n function DOMAIN_SEPARATOR() public view returns (bytes32) {\\n // As explained in EIP-2612, if the DOMAIN_SEPARATOR contains the\\n // chainId and is defined at contract deployment instead of\\n // reconstructed for every signature, there is a risk of possible replay\\n // attacks between chains in the event of a future chain split.\\n // To address this issue, we check the cached chain ID against the\\n // current one and in case they are different, we build domain separator\\n // from scratch.\\n if (block.chainid == cachedChainId) {\\n return cachedDomainSeparator;\\n } else {\\n return buildDomainSeparator();\\n }\\n }\\n\\n function _increaseBalance(address recipient, uint256 amount) internal {\\n require(\\n recipient != address(this),\\n \\\"Can not increase balance for Bank\\\"\\n );\\n balanceOf[recipient] += amount;\\n emit BalanceIncreased(recipient, amount);\\n }\\n\\n function _transferBalance(\\n address spender,\\n address recipient,\\n uint256 amount\\n ) private {\\n require(\\n recipient != address(0),\\n \\\"Can not transfer to the zero address\\\"\\n );\\n require(\\n recipient != address(this),\\n \\\"Can not transfer to the Bank address\\\"\\n );\\n\\n uint256 spenderBalance = balanceOf[spender];\\n require(spenderBalance >= amount, \\\"Transfer amount exceeds balance\\\");\\n unchecked {\\n balanceOf[spender] = spenderBalance - amount;\\n }\\n balanceOf[recipient] += amount;\\n emit BalanceTransferred(spender, recipient, amount);\\n }\\n\\n function _approveBalance(\\n address owner,\\n address spender,\\n uint256 amount\\n ) private {\\n require(spender != address(0), \\\"Can not approve to the zero address\\\");\\n allowance[owner][spender] = amount;\\n emit BalanceApproved(owner, spender, amount);\\n }\\n\\n function buildDomainSeparator() private view returns (bytes32) {\\n return\\n keccak256(\\n abi.encode(\\n keccak256(\\n \\\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\\\"\\n ),\\n keccak256(bytes(\\\"TBTC Bank\\\")),\\n keccak256(bytes(\\\"1\\\")),\\n block.chainid,\\n address(this)\\n )\\n );\\n }\\n}\\n\",\"keccak256\":\"0x318005485bc8fb8a8fb6091bc4a3ca0e304693d8b372b61835bed2f1f735faf7\",\"license\":\"GPL-3.0-only\"},\"contracts/bank/IReceiveBalanceApproval.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\n/// @title IReceiveBalanceApproval\\n/// @notice `IReceiveBalanceApproval` is an interface for a smart contract\\n/// consuming Bank balances approved to them in the same transaction by\\n/// other contracts or externally owned accounts (EOA).\\ninterface IReceiveBalanceApproval {\\n /// @notice Called by the Bank in `approveBalanceAndCall` function after\\n /// the balance `owner` approved `amount` of their balance in the\\n /// Bank for the contract. This way, the depositor can approve\\n /// balance and call the contract to use the approved balance in\\n /// a single transaction.\\n /// @param owner Address of the Bank balance owner who approved their\\n /// balance to be used by the contract.\\n /// @param amount The amount of the Bank balance approved by the owner\\n /// to be used by the contract.\\n /// @param extraData The `extraData` passed to `Bank.approveBalanceAndCall`.\\n /// @dev The implementation must ensure this function can only be called\\n /// by the Bank. The Bank does _not_ guarantee that the `amount`\\n /// approved by the `owner` currently exists on their balance. That is,\\n /// the `owner` could approve more balance than they currently have.\\n /// This works the same as `Bank.approve` function. The contract must\\n /// ensure the actual balance is checked before performing any action\\n /// based on it.\\n function receiveBalanceApproval(\\n address owner,\\n uint256 amount,\\n bytes calldata extraData\\n ) external;\\n}\\n\",\"keccak256\":\"0x864f29d54d9d672348520b1f46bbce786994e07d86032987e4374a267a345c2b\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/BitcoinTx.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\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 pure\\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 // The output consists of an 8-byte value and a variable length script.\\n // To extract just the script, we ignore the first 8 bytes.\\n uint256 scriptLen = output.length - 8;\\n\\n // The P2PKH script is 26 bytes long.\\n // The P2WPKH script is 23 bytes long.\\n // A valid script must have one of these lengths,\\n // and we can identify the expected script type by the length.\\n require(\\n scriptLen == 26 || scriptLen == 23,\\n \\\"Output must be P2PKH or P2WPKH\\\"\\n );\\n\\n if (scriptLen == 26) {\\n // Compare to the expected P2PKH script.\\n bytes26 script = bytes26(output.slice32(8));\\n\\n require(\\n script == makeP2PKHScript(pubKeyHash),\\n \\\"Invalid P2PKH script\\\"\\n );\\n }\\n\\n if (scriptLen == 23) {\\n // Compare to the expected P2WPKH script.\\n bytes23 script = bytes23(output.slice32(8));\\n\\n require(\\n script == makeP2WPKHScript(pubKeyHash),\\n \\\"Invalid P2WPKH script\\\"\\n );\\n }\\n\\n return pubKeyHash;\\n }\\n\\n /// @notice Build the P2PKH script from the given public key hash.\\n /// @param pubKeyHash The 20-byte public key hash.\\n /// @return The P2PKH script.\\n /// @dev The P2PKH script has the following byte format:\\n /// <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 function makeP2PKHScript(bytes20 pubKeyHash)\\n internal\\n pure\\n returns (bytes26)\\n {\\n bytes26 P2PKHScriptMask = hex\\\"1976a914000000000000000000000000000000000000000088ac\\\";\\n\\n return ((bytes26(pubKeyHash) >> 32) | P2PKHScriptMask);\\n }\\n\\n /// @notice Build the P2WPKH script from the given public key hash.\\n /// @param pubKeyHash The 20-byte public key hash.\\n /// @return The P2WPKH script.\\n /// @dev The P2WPKH script 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 function makeP2WPKHScript(bytes20 pubKeyHash)\\n internal\\n pure\\n returns (bytes23)\\n {\\n bytes23 P2WPKHScriptMask = hex\\\"1600140000000000000000000000000000000000000000\\\";\\n\\n return ((bytes23(pubKeyHash) >> 24) | P2WPKHScriptMask);\\n }\\n}\\n\",\"keccak256\":\"0xb087cb5b364bcdcca63772a442c630e858c90555fc691521a212c068b89120a5\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Bridge.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@keep-network/random-beacon/contracts/Governable.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/ReimbursementPool.sol\\\";\\nimport {IWalletOwner as EcdsaWalletOwner} from \\\"@keep-network/ecdsa/contracts/api/IWalletOwner.sol\\\";\\n\\nimport \\\"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol\\\";\\nimport \\\"@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol\\\";\\n\\nimport \\\"./IRelay.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Deposit.sol\\\";\\nimport \\\"./DepositSweep.sol\\\";\\nimport \\\"./Redemption.sol\\\";\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./EcdsaLib.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\nimport \\\"./Fraud.sol\\\";\\nimport \\\"./MovingFunds.sol\\\";\\n\\nimport \\\"../bank/IReceiveBalanceApproval.sol\\\";\\nimport \\\"../bank/Bank.sol\\\";\\n\\n/// @title Bitcoin Bridge\\n/// @notice Bridge manages BTC deposit and redemption flow and is increasing and\\n/// decreasing balances in the Bank as a result of BTC deposit and\\n/// redemption operations performed by depositors and redeemers.\\n///\\n/// Depositors send BTC funds to the most recently created off-chain\\n/// ECDSA wallet of the bridge using pay-to-script-hash (P2SH) or\\n/// pay-to-witness-script-hash (P2WSH) containing hashed information\\n/// about the depositor\\u2019s Ethereum address. Then, the depositor reveals\\n/// their Ethereum address along with their deposit blinding factor,\\n/// refund public key hash and refund locktime to the Bridge on Ethereum\\n/// chain. The off-chain ECDSA wallet listens for these sorts of\\n/// messages and when it gets one, it checks the Bitcoin network to make\\n/// sure the deposit lines up. If it does, the off-chain ECDSA wallet\\n/// may decide to pick the deposit transaction for sweeping, and when\\n/// the sweep operation is confirmed on the Bitcoin network, the ECDSA\\n/// wallet informs the Bridge about the sweep increasing appropriate\\n/// balances in the Bank.\\n/// @dev Bridge is an upgradeable component of the Bank. The order of\\n/// functionalities in this contract is: deposit, sweep, redemption,\\n/// moving funds, wallet lifecycle, frauds, parameters.\\ncontract Bridge is\\n Governable,\\n EcdsaWalletOwner,\\n Initializable,\\n IReceiveBalanceApproval\\n{\\n using BridgeState for BridgeState.Storage;\\n using Deposit for BridgeState.Storage;\\n using DepositSweep for BridgeState.Storage;\\n using Redemption for BridgeState.Storage;\\n using MovingFunds for BridgeState.Storage;\\n using Wallets for BridgeState.Storage;\\n using Fraud for BridgeState.Storage;\\n\\n BridgeState.Storage internal self;\\n\\n event DepositRevealed(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex,\\n address indexed depositor,\\n uint64 amount,\\n bytes8 blindingFactor,\\n bytes20 indexed walletPubKeyHash,\\n bytes20 refundPubKeyHash,\\n bytes4 refundLocktime,\\n address vault\\n );\\n\\n event DepositsSwept(bytes20 walletPubKeyHash, bytes32 sweepTxHash);\\n\\n event RedemptionRequested(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript,\\n address indexed redeemer,\\n uint64 requestedAmount,\\n uint64 treasuryFee,\\n uint64 txMaxFee\\n );\\n\\n event RedemptionsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 redemptionTxHash\\n );\\n\\n event RedemptionTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript\\n );\\n\\n event WalletMovingFunds(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event MovingFundsCommitmentSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes20[] targetWallets,\\n address submitter\\n );\\n\\n event MovingFundsTimeoutReset(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash\\n );\\n\\n event MovingFundsTimedOut(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsBelowDustReported(bytes20 indexed walletPubKeyHash);\\n\\n event MovedFundsSwept(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sweepTxHash\\n );\\n\\n event MovedFundsSweepTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex\\n );\\n\\n event NewWalletRequested();\\n\\n event NewWalletRegistered(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosing(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosed(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletTerminated(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event FraudChallengeSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n );\\n\\n event FraudChallengeDefeated(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash\\n );\\n\\n event FraudChallengeDefeatTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash\\n );\\n\\n event VaultStatusUpdated(address indexed vault, bool isTrusted);\\n\\n event SpvMaintainerStatusUpdated(\\n address indexed spvMaintainer,\\n bool isTrusted\\n );\\n\\n event DepositParametersUpdated(\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n );\\n\\n event RedemptionParametersUpdated(\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n );\\n\\n event MovingFundsParametersUpdated(\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n\\n event WalletParametersUpdated(\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n );\\n\\n event FraudParametersUpdated(\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n );\\n\\n event TreasuryUpdated(address treasury);\\n\\n modifier onlySpvMaintainer() {\\n require(\\n self.isSpvMaintainer[msg.sender],\\n \\\"Caller is not SPV maintainer\\\"\\n );\\n _;\\n }\\n\\n /// @custom:oz-upgrades-unsafe-allow constructor\\n constructor() {\\n _disableInitializers();\\n }\\n\\n /// @dev Initializes upgradable contract on deployment.\\n /// @param _bank Address of the Bank the Bridge belongs to.\\n /// @param _relay Address of the Bitcoin relay providing the current Bitcoin\\n /// network difficulty.\\n /// @param _treasury Address where the deposit and redemption treasury fees\\n /// will be sent to.\\n /// @param _ecdsaWalletRegistry Address of the ECDSA Wallet Registry contract.\\n /// @param _reimbursementPool Address of the Reimbursement Pool contract.\\n /// @param _txProofDifficultyFactor The number of confirmations on the Bitcoin\\n /// chain required to successfully evaluate an SPV proof.\\n function initialize(\\n address _bank,\\n address _relay,\\n address _treasury,\\n address _ecdsaWalletRegistry,\\n address payable _reimbursementPool,\\n uint96 _txProofDifficultyFactor\\n ) external initializer {\\n require(_bank != address(0), \\\"Bank address cannot be zero\\\");\\n self.bank = Bank(_bank);\\n\\n require(_relay != address(0), \\\"Relay address cannot be zero\\\");\\n self.relay = IRelay(_relay);\\n\\n require(\\n _ecdsaWalletRegistry != address(0),\\n \\\"ECDSA Wallet Registry address cannot be zero\\\"\\n );\\n self.ecdsaWalletRegistry = EcdsaWalletRegistry(_ecdsaWalletRegistry);\\n\\n require(\\n _reimbursementPool != address(0),\\n \\\"Reimbursement Pool address cannot be zero\\\"\\n );\\n self.reimbursementPool = ReimbursementPool(_reimbursementPool);\\n\\n require(_treasury != address(0), \\\"Treasury address cannot be zero\\\");\\n self.treasury = _treasury;\\n\\n self.txProofDifficultyFactor = _txProofDifficultyFactor;\\n\\n //\\n // All parameters set in the constructor are initial ones, used at the\\n // moment contracts were deployed for the first time. Parameters are\\n // governable and values assigned in the constructor do not need to\\n // reflect the current ones. Keep in mind the initial parameters are\\n // pretty forgiving and valid only for the early stage of the network.\\n //\\n\\n self.depositDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC\\n self.depositTxMaxFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.depositRevealAheadPeriod = 15 days;\\n self.depositTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005\\n self.redemptionDustThreshold = 1000000; // 1000000 satoshi = 0.01 BTC\\n self.redemptionTreasuryFeeDivisor = 2000; // 1/2000 == 5bps == 0.05% == 0.0005\\n self.redemptionTxMaxFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.redemptionTxMaxTotalFee = 1000000; // 1000000 satoshi = 0.01 BTC\\n self.redemptionTimeout = 5 days;\\n self.redemptionTimeoutSlashingAmount = 100 * 1e18; // 100 T\\n self.redemptionTimeoutNotifierRewardMultiplier = 100; // 100%\\n self.movingFundsTxMaxTotalFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.movingFundsDustThreshold = 200000; // 200000 satoshi = 0.002 BTC\\n self.movingFundsTimeoutResetDelay = 6 days;\\n self.movingFundsTimeout = 7 days;\\n self.movingFundsTimeoutSlashingAmount = 100 * 1e18; // 100 T\\n self.movingFundsTimeoutNotifierRewardMultiplier = 100; //100%\\n self.movingFundsCommitmentGasOffset = 15000;\\n self.movedFundsSweepTxMaxTotalFee = 100000; // 100000 satoshi = 0.001 BTC\\n self.movedFundsSweepTimeout = 7 days;\\n self.movedFundsSweepTimeoutSlashingAmount = 100 * 1e18; // 100 T\\n self.movedFundsSweepTimeoutNotifierRewardMultiplier = 100; //100%\\n self.fraudChallengeDepositAmount = 5 ether;\\n self.fraudChallengeDefeatTimeout = 7 days;\\n self.fraudSlashingAmount = 100 * 1e18; // 100 T\\n self.fraudNotifierRewardMultiplier = 100; // 100%\\n self.walletCreationPeriod = 1 weeks;\\n self.walletCreationMinBtcBalance = 1e8; // 1 BTC\\n self.walletCreationMaxBtcBalance = 100e8; // 100 BTC\\n self.walletClosureMinBtcBalance = 5 * 1e7; // 0.5 BTC\\n self.walletMaxAge = 26 weeks; // ~6 months\\n self.walletMaxBtcTransfer = 10e8; // 10 BTC\\n self.walletClosingPeriod = 40 days;\\n\\n _transferGovernance(msg.sender);\\n }\\n\\n /// @notice Used by the depositor to reveal information about their P2(W)SH\\n /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain\\n /// wallet listens for revealed deposit events and may decide to\\n /// include the revealed deposit in the next executed sweep.\\n /// Information about the Bitcoin deposit can be revealed before or\\n /// after the Bitcoin transaction with P2(W)SH deposit is mined on\\n /// the Bitcoin chain. Worth noting, the gas cost of this function\\n /// scales with the number of P2(W)SH transaction inputs and\\n /// outputs. The deposit may be routed to one of the trusted vaults.\\n /// When a deposit is routed to a vault, vault gets notified when\\n /// the deposit gets swept and it may execute the appropriate action.\\n /// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`.\\n /// @param reveal Deposit reveal data, see `RevealInfo struct.\\n /// @dev Requirements:\\n /// - This function must be called by the same Ethereum address as the\\n /// one used in the P2(W)SH BTC deposit transaction as a depositor,\\n /// - `reveal.walletPubKeyHash` must identify a `Live` wallet,\\n /// - `reveal.vault` must be 0x0 or point to a trusted vault,\\n /// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH\\n /// output of the BTC deposit transaction,\\n /// - `reveal.blindingFactor` must be the blinding factor used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundPubKeyHash` must be the refund pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundLocktime` must be the refund locktime used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`\\n /// can be revealed only one time.\\n ///\\n /// If any of these requirements is not met, the wallet _must_ refuse\\n /// to sweep the deposit and the depositor has to wait until the\\n /// deposit script unlocks to receive their BTC back.\\n function revealDeposit(\\n BitcoinTx.Info calldata fundingTx,\\n Deposit.DepositRevealInfo calldata reveal\\n ) external {\\n self.revealDeposit(fundingTx, reveal);\\n }\\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 /// @param vault Optional address of the vault where all swept deposits\\n /// should be routed to. All deposits swept as part of the transaction\\n /// must have their `vault` parameters set to the same address.\\n /// If this parameter is set to an address of a trusted vault, swept\\n /// deposits are routed to that vault.\\n /// If this parameter is set to the zero address or to an address\\n /// of a non-trusted vault, swept deposits are not routed to a\\n /// vault but depositors' balances are increased in the Bank\\n /// individually.\\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 /// - All revealed deposits that are swept by `sweepTx` must have\\n /// their `vault` parameters set to the same address as the address\\n /// passed in the `vault` function parameter,\\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 BitcoinTx.Info calldata sweepTx,\\n BitcoinTx.Proof calldata sweepProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n address vault\\n ) external onlySpvMaintainer {\\n self.submitDepositSweepProof(sweepTx, sweepProof, mainUtxo, vault);\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script. Handles the\\n /// simplest case in which the redeemer's balance is decreased in\\n /// the Bank.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to process the request,\\n /// - Redeemer must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes calldata redeemerOutputScript,\\n uint64 amount\\n ) external {\\n self.requestRedemption(\\n walletPubKeyHash,\\n mainUtxo,\\n msg.sender,\\n redeemerOutputScript,\\n amount\\n );\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script. Used by\\n /// `Bank.approveBalanceAndCall`. Can handle more complex cases\\n /// where balance owner may be someone else than the redeemer.\\n /// For example, vault redeeming its balance for some depositor.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @param redemptionData ABI-encoded redemption data:\\n /// [\\n /// address redeemer,\\n /// bytes20 walletPubKeyHash,\\n /// bytes32 mainUtxoTxHash,\\n /// uint32 mainUtxoTxOutputIndex,\\n /// uint64 mainUtxoTxOutputValue,\\n /// bytes redeemerOutputScript\\n /// ]\\n ///\\n /// - redeemer: The Ethereum address of the redeemer who will be able\\n /// to claim Bank balance if anything goes wrong during the redemption.\\n /// In the most basic case, when someone redeems their balance\\n /// from the Bank, `balanceOwner` is the same as `redeemer`.\\n /// However, when a Vault is redeeming part of its balance for some\\n /// redeemer address (for example, someone who has earlier deposited\\n /// into that Vault), `balanceOwner` is the Vault, and `redeemer` is\\n /// the address for which the vault is redeeming its balance to,\\n /// - walletPubKeyHash: The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key),\\n /// - mainUtxoTxHash: Data of the wallet's main UTXO TX hash, as\\n /// currently known on the Ethereum chain,\\n /// - mainUtxoTxOutputIndex: Data of the wallet's main UTXO output\\n /// index, as currently known on Ethereum chain,\\n /// - mainUtxoTxOutputValue: Data of the wallet's main UTXO output\\n /// value, as currently known on Ethereum chain,\\n /// - redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @dev Requirements:\\n /// - The caller must be the Bank,\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to process the request.\\n ///\\n /// Note on upgradeability:\\n /// Bridge is an upgradeable contract deployed behind\\n /// a TransparentUpgradeableProxy. Accepting redemption data as bytes\\n /// provides great flexibility. The Bridge is just like any other\\n /// contract with a balance approved in the Bank and can be upgraded\\n /// to another version without being bound to a particular interface\\n /// forever. This flexibility comes with the cost - developers\\n /// integrating their vaults and dApps with `Bridge` using\\n /// `approveBalanceAndCall` need to pay extra attention to\\n /// `redemptionData` and adjust the code in case the expected structure\\n /// of `redemptionData` changes.\\n function receiveBalanceApproval(\\n address balanceOwner,\\n uint256 amount,\\n bytes calldata redemptionData\\n ) external override {\\n require(msg.sender == address(self.bank), \\\"Caller is not the bank\\\");\\n\\n self.requestRedemption(\\n balanceOwner,\\n SafeCastUpgradeable.toUint64(amount),\\n redemptionData\\n );\\n }\\n\\n /// @notice Used by the wallet to prove the BTC redemption transaction\\n /// and to make the necessary bookkeeping. Redemption is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function is performing Bank balance updates by burning\\n /// the total redeemed Bitcoin amount from Bridge balance and\\n /// transferring the treasury fee sum to the treasury address.\\n ///\\n /// It is possible to prove the given redemption only one time.\\n /// @param redemptionTx Bitcoin redemption transaction data.\\n /// @param redemptionProof Bitcoin redemption proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @dev Requirements:\\n /// - `redemptionTx` 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 `redemptionTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs handling existing pending\\n /// redemption requests or pointing to reported timed out requests.\\n /// There can be also 1 optional output representing the\\n /// change and pointing back to the 20-byte wallet public key hash.\\n /// The change should be always present if the redeemed value sum\\n /// is lower than the total wallet's BTC balance,\\n /// - `redemptionProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input.\\n /// Other remarks:\\n /// - Putting the change output as the first transaction output can\\n /// save some gas because the output processing loop begins each\\n /// iteration by checking whether the given output is the change\\n /// thus uses some gas for making the comparison. Once the change\\n /// is identified, that check is omitted in further iterations.\\n function submitRedemptionProof(\\n BitcoinTx.Info calldata redemptionTx,\\n BitcoinTx.Proof calldata redemptionProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external onlySpvMaintainer {\\n self.submitRedemptionProof(\\n redemptionTx,\\n redemptionProof,\\n mainUtxo,\\n walletPubKeyHash\\n );\\n }\\n\\n /// @notice Notifies that there is a pending redemption request associated\\n /// with the given wallet, that has timed out. The redemption\\n /// request is identified by the key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The results of calling this function:\\n /// - The pending redemptions value for the wallet will be decreased\\n /// by the requested amount (minus treasury fee),\\n /// - The tokens taken from the redeemer on redemption request will\\n /// be returned to the redeemer,\\n /// - The request will be moved from pending redemptions to\\n /// timed-out redemptions,\\n /// - If the state of the wallet is `Live` or `MovingFunds`, the\\n /// wallet operators will be slashed and the notifier will be\\n /// rewarded,\\n /// - If the state of wallet is `Live`, the wallet will be closed or\\n /// marked as `MovingFunds` (depending on the presence or absence\\n /// of the wallet's main UTXO) and the wallet will no longer be\\n /// marked as the active wallet (if it was marked as such).\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH).\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Terminated state,\\n /// - The redemption request identified by `walletPubKeyHash` and\\n /// `redeemerOutputScript` must exist,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time defined by `redemptionTimeout` must have\\n /// passed since the redemption was requested (the request must be\\n /// timed-out).\\n function notifyRedemptionTimeout(\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs,\\n bytes calldata redeemerOutputScript\\n ) external {\\n self.notifyRedemptionTimeout(\\n walletPubKeyHash,\\n walletMembersIDs,\\n redeemerOutputScript\\n );\\n }\\n\\n /// @notice Submits the moving funds target wallets commitment.\\n /// Once all requirements are met, that function registers the\\n /// target wallets commitment and opens the way for moving funds\\n /// proof submission.\\n /// The caller is reimbursed for the transaction costs.\\n /// @param walletPubKeyHash 20-byte public key hash of the source wallet.\\n /// @param walletMainUtxo Data of the source wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @param walletMembersIDs Identifiers of the source wallet signing group\\n /// members.\\n /// @param walletMemberIndex Position of the caller in the source wallet\\n /// signing group members list.\\n /// @param targetWallets List of 20-byte public key hashes of the target\\n /// wallets that the source wallet commits to move the funds to.\\n /// @dev Requirements:\\n /// - The source wallet must be in the MovingFunds state,\\n /// - The source wallet must not have pending redemption requests,\\n /// - The source wallet must not have pending moved funds sweep requests,\\n /// - The source wallet must not have submitted its commitment already,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given source wallet in the ECDSA registry. Those IDs are\\n /// not directly stored in the contract for gas efficiency purposes\\n /// but they can be read from appropriate `DkgResultSubmitted`\\n /// and `DkgResultApproved` events,\\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],\\n /// - The caller must be the member of the source wallet signing group\\n /// at the position indicated by `walletMemberIndex` parameter,\\n /// - The `walletMainUtxo` components must point to the recent main\\n /// UTXO of the source wallet, as currently known on the Ethereum\\n /// chain,\\n /// - Source wallet BTC balance must be greater than zero,\\n /// - At least one Live wallet must exist in the system,\\n /// - Submitted target wallets count must match the expected count\\n /// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`\\n /// where `N > 0`,\\n /// - Each target wallet must be not equal to the source wallet,\\n /// - Each target wallet must follow the expected order i.e. all\\n /// target wallets 20-byte public key hashes represented as numbers\\n /// must form a strictly increasing sequence without duplicates,\\n /// - Each target wallet must be in Live state.\\n function submitMovingFundsCommitment(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo,\\n uint32[] calldata walletMembersIDs,\\n uint256 walletMemberIndex,\\n bytes20[] calldata targetWallets\\n ) external {\\n uint256 gasStart = gasleft();\\n\\n self.submitMovingFundsCommitment(\\n walletPubKeyHash,\\n walletMainUtxo,\\n walletMembersIDs,\\n walletMemberIndex,\\n targetWallets\\n );\\n\\n self.reimbursementPool.refund(\\n (gasStart - gasleft()) + self.movingFundsCommitmentGasOffset,\\n msg.sender\\n );\\n }\\n\\n /// @notice Resets the moving funds timeout for the given wallet if the\\n /// target wallet commitment cannot be submitted due to a lack\\n /// of live wallets in the system.\\n /// @param walletPubKeyHash 20-byte public key hash of the moving funds wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The target wallets commitment must not be already submitted for\\n /// the given moving funds wallet,\\n /// - Live wallets count must be zero,\\n /// - The moving funds timeout reset delay must be elapsed.\\n function resetMovingFundsTimeout(bytes20 walletPubKeyHash) external {\\n self.resetMovingFundsTimeout(walletPubKeyHash);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moving funds transaction\\n /// and to make the necessary state changes. Moving funds is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the moving funds transaction structure\\n /// by checking if it actually spends the main UTXO of the declared\\n /// wallet and locks the value on the pre-committed target wallets\\n /// using a reasonable transaction fee. If all preconditions are\\n /// met, this functions closes the source wallet.\\n ///\\n /// It is possible to prove the given moving funds transaction only\\n /// one time.\\n /// @param movingFundsTx Bitcoin moving funds transaction data.\\n /// @param movingFundsProof Bitcoin moving funds proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet\\n /// which performed the moving funds transaction.\\n /// @dev Requirements:\\n /// - `movingFundsTx` 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 `movingFundsTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs corresponding to the\\n /// pre-committed target wallets. Outputs must be ordered in the\\n /// same way as their corresponding target wallets are ordered\\n /// within the target wallets commitment,\\n /// - `movingFundsProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input,\\n /// - The wallet that `walletPubKeyHash` points to must be in the\\n /// MovingFunds state,\\n /// - The target wallets commitment must be submitted by the wallet\\n /// that `walletPubKeyHash` points to,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movingFundsTxMaxTotalFee` governable parameter.\\n function submitMovingFundsProof(\\n BitcoinTx.Info calldata movingFundsTx,\\n BitcoinTx.Proof calldata movingFundsProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external onlySpvMaintainer {\\n self.submitMovingFundsProof(\\n movingFundsTx,\\n movingFundsProof,\\n mainUtxo,\\n walletPubKeyHash\\n );\\n }\\n\\n /// @notice Notifies about a timed out moving funds process. Terminates\\n /// the wallet and slashes signing group members as a result.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The moving funds timeout must be actually exceeded,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovingFundsTimeout(\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n self.notifyMovingFundsTimeout(walletPubKeyHash, walletMembersIDs);\\n }\\n\\n /// @notice Notifies about a moving funds wallet whose BTC balance is\\n /// below the moving funds dust threshold. Ends the moving funds\\n /// process and begins wallet closing immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known\\n /// on the Ethereum chain.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain.\\n /// If the wallet has no main UTXO, this parameter can be empty as it\\n /// is ignored,\\n /// - The wallet BTC balance must be below the moving funds threshold.\\n function notifyMovingFundsBelowDust(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external {\\n self.notifyMovingFundsBelowDust(walletPubKeyHash, mainUtxo);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moved funds sweep\\n /// transaction and to make the necessary state changes. Moved\\n /// funds sweep is only accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the sweep transaction structure by\\n /// checking if it actually spends the moved funds UTXO and the\\n /// sweeping wallet's main UTXO (optionally), and if it locks the\\n /// value on the sweeping wallet's 20-byte public key hash using a\\n /// reasonable transaction fee. If all preconditions are\\n /// met, this function updates the sweeping wallet main UTXO, thus\\n /// their BTC balance.\\n ///\\n /// It is possible to prove the given sweep transaction only\\n /// one time.\\n /// @param sweepTx Bitcoin sweep funds transaction data.\\n /// @param sweepProof Bitcoin sweep funds proof data.\\n /// @param mainUtxo Data of the sweeping wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\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\\n /// the first input pointing to a moved funds sweep request targeted\\n /// to the wallet, and optionally, the second input pointing to the\\n /// wallet's main UTXO, if the sweeping wallet has a main UTXO set.\\n /// There should be only one output locking funds on the sweeping\\n /// wallet 20-byte public key hash,\\n /// - `sweepProof` components must match the expected structure.\\n /// See `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 sweeping wallet, as currently known on the Ethereum chain.\\n /// If there is no main UTXO, this parameter is ignored,\\n /// - The sweeping wallet must be in the Live or MovingFunds state,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movedFundsSweepTxMaxTotalFee` governable parameter.\\n function submitMovedFundsSweepProof(\\n BitcoinTx.Info calldata sweepTx,\\n BitcoinTx.Proof calldata sweepProof,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external onlySpvMaintainer {\\n self.submitMovedFundsSweepProof(sweepTx, sweepProof, mainUtxo);\\n }\\n\\n /// @notice Notifies about a timed out moved funds sweep process. If the\\n /// wallet is not terminated yet, that function terminates\\n /// the wallet and slashes signing group members as a result.\\n /// Marks the given sweep request as TimedOut.\\n /// @param movingFundsTxHash 32-byte hash of the moving funds transaction\\n /// that caused the sweep request to be created.\\n /// @param movingFundsTxOutputIndex Index of the moving funds transaction\\n /// output that is subject of the sweep request.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The moved funds sweep request must be in the Pending state,\\n /// - The moved funds sweep timeout must be actually exceeded,\\n /// - The wallet must be either in the Live or MovingFunds or\\n /// Terminated state,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovedFundsSweepTimeout(\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n self.notifyMovedFundsSweepTimeout(\\n movingFundsTxHash,\\n movingFundsTxOutputIndex,\\n walletMembersIDs\\n );\\n }\\n\\n /// @notice Requests creation of a new wallet. This function just\\n /// forms a request and the creation process is performed\\n /// asynchronously. Once a wallet is created, the ECDSA Wallet\\n /// Registry will notify this contract by calling the\\n /// `__ecdsaWalletCreatedCallback` function.\\n /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - `activeWalletMainUtxo` components must point to the recent main\\n /// UTXO of the given active wallet, as currently known on the\\n /// Ethereum chain. If there is no active wallet at the moment, or\\n /// the active wallet has no main UTXO, this parameter can be\\n /// empty as it is ignored,\\n /// - Wallet creation must not be in progress,\\n /// - If the active wallet is set, one of the following\\n /// conditions must be true:\\n /// - The active wallet BTC balance is above the minimum threshold\\n /// and the active wallet is old enough, i.e. the creation period\\n /// was elapsed since its creation time,\\n /// - The active wallet BTC balance is above the maximum threshold.\\n function requestNewWallet(BitcoinTx.UTXO calldata activeWalletMainUtxo)\\n external\\n {\\n self.requestNewWallet(activeWalletMainUtxo);\\n }\\n\\n /// @notice A callback function that is called by the ECDSA Wallet Registry\\n /// once a new ECDSA wallet is created.\\n /// @param ecdsaWalletID Wallet's unique identifier.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Given wallet data must not belong to an already registered wallet.\\n function __ecdsaWalletCreatedCallback(\\n bytes32 ecdsaWalletID,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external override {\\n self.registerNewWallet(ecdsaWalletID, publicKeyX, publicKeyY);\\n }\\n\\n /// @notice A callback function that is called by the ECDSA Wallet Registry\\n /// once a wallet heartbeat failure is detected.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Wallet must be in Live state.\\n function __ecdsaWalletHeartbeatFailedCallback(\\n bytes32,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external override {\\n self.notifyWalletHeartbeatFailed(publicKeyX, publicKeyY);\\n }\\n\\n /// @notice Notifies that the wallet is either old enough or has too few\\n /// satoshi left and qualifies to be closed.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - Wallet must not be set as the current active wallet,\\n /// - Wallet must exceed the wallet maximum age OR the wallet BTC\\n /// balance must be lesser than the minimum threshold. If the latter\\n /// case is true, the `walletMainUtxo` components must point to the\\n /// recent main UTXO of the given wallet, as currently known on the\\n /// Ethereum chain. If the wallet has no main UTXO, this parameter\\n /// can be empty as it is ignored since the wallet balance is\\n /// assumed to be zero,\\n /// - Wallet must be in Live state.\\n function notifyWalletCloseable(\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo\\n ) external {\\n self.notifyWalletCloseable(walletPubKeyHash, walletMainUtxo);\\n }\\n\\n /// @notice Notifies about the end of the closing period for the given wallet.\\n /// Closes the wallet ultimately and notifies the ECDSA registry\\n /// about this fact.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the Closing state,\\n /// - The wallet closing period must have elapsed.\\n function notifyWalletClosingPeriodElapsed(bytes20 walletPubKeyHash)\\n external\\n {\\n self.notifyWalletClosingPeriodElapsed(walletPubKeyHash);\\n }\\n\\n /// @notice Submits a fraud challenge indicating that a UTXO being under\\n /// wallet control was unlocked by the wallet but was not used\\n /// according to the protocol rules. That means the wallet signed\\n /// a transaction input pointing to that UTXO and there is a unique\\n /// sighash and signature pair associated with that input. This\\n /// function uses those parameters to create a fraud accusation that\\n /// proves a given transaction input unlocking the given UTXO was\\n /// actually signed by the wallet. This function cannot determine\\n /// whether the transaction was actually broadcast and the input was\\n /// consumed in a fraudulent way so it just opens a challenge period\\n /// during which the wallet can defeat the challenge by submitting\\n /// proof of a transaction that consumes the given input according\\n /// to protocol rules. To prevent spurious allegations, the caller\\n /// must deposit ETH that is returned back upon justified fraud\\n /// challenge or confiscated otherwise.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @param signature Bitcoin signature in the R/S/V format.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPublicKey` must be in Live or MovingFunds\\n /// or Closing state,\\n /// - The challenger must send appropriate amount of ETH used as\\n /// fraud challenge deposit,\\n /// - The signature (represented by r, s and v) must be generated by\\n /// the wallet behind `walletPubKey` during signing of `sighash`\\n /// which was calculated from `preimageSha256`,\\n /// - Wallet can be challenged for the given signature only once.\\n function submitFraudChallenge(\\n bytes calldata walletPublicKey,\\n bytes memory preimageSha256,\\n BitcoinTx.RSVSignature calldata signature\\n ) external payable {\\n self.submitFraudChallenge(walletPublicKey, preimageSha256, signature);\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet if\\n /// the transaction that spends the UTXO follows the protocol rules.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during input signing.\\n /// The fraud challenge defeat attempt will only succeed if the\\n /// inputs in the preimage are considered honestly spent by the\\n /// wallet. Therefore the transaction spending the UTXO must be\\n /// proven in the Bridge before a challenge defeat is called.\\n /// If successfully defeated, the fraud challenge is marked as\\n /// resolved and the amount of ether deposited by the challenger is\\n /// sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference.\\n /// @param witness Flag indicating whether the preimage was produced for a\\n /// witness input. True for witness, false for non-witness input.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`\\n /// must identify an open fraud challenge,\\n /// - the preimage must be a valid preimage of a transaction generated\\n /// according to the protocol rules and already proved in the Bridge,\\n /// - before a defeat attempt is made the transaction that spends the\\n /// given UTXO must be proven in the Bridge.\\n function defeatFraudChallenge(\\n bytes calldata walletPublicKey,\\n bytes calldata preimage,\\n bool witness\\n ) external {\\n self.defeatFraudChallenge(walletPublicKey, preimage, witness);\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet by\\n /// proving the sighash and signature were produced for an off-chain\\n /// wallet heartbeat message following a strict format.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during heartbeat message\\n /// signing. The fraud challenge defeat attempt will only succeed if\\n /// the signed message follows a strict format required for\\n /// heartbeat messages. If successfully defeated, the fraud\\n /// challenge is marked as resolved and the amount of ether\\n /// deposited by the challenger is sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param heartbeatMessage Off-chain heartbeat message meeting the heartbeat\\n /// message format requirements which produces sighash used to\\n /// generate the ECDSA signature that is the subject of the fraud\\n /// claim.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as\\n /// `hash256(heartbeatMessage)` must identify an open fraud challenge,\\n /// - `heartbeatMessage` must follow a strict format of heartbeat\\n /// messages.\\n function defeatFraudChallengeWithHeartbeat(\\n bytes calldata walletPublicKey,\\n bytes calldata heartbeatMessage\\n ) external {\\n self.defeatFraudChallengeWithHeartbeat(\\n walletPublicKey,\\n heartbeatMessage\\n );\\n }\\n\\n /// @notice Notifies about defeat timeout for the given fraud challenge.\\n /// Can be called only if there was a fraud challenge identified by\\n /// the provided `walletPublicKey` and `sighash` and it was not\\n /// defeated on time. The amount of time that needs to pass after\\n /// a fraud challenge is reported is indicated by the\\n /// `challengeDefeatTimeout`. After a successful fraud challenge\\n /// defeat timeout notification the fraud challenge is marked as\\n /// resolved, the stake of each operator is slashed, the ether\\n /// deposited is returned to the challenger and the challenger is\\n /// rewarded.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Closing or\\n /// Terminated state,\\n /// - The `walletPublicKey` and `sighash` calculated from\\n /// `preimageSha256` must identify an open fraud challenge,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time indicated by `challengeDefeatTimeout` must pass\\n /// after the challenge was reported.\\n function notifyFraudChallengeDefeatTimeout(\\n bytes calldata walletPublicKey,\\n uint32[] calldata walletMembersIDs,\\n bytes memory preimageSha256\\n ) external {\\n self.notifyFraudChallengeDefeatTimeout(\\n walletPublicKey,\\n walletMembersIDs,\\n preimageSha256\\n );\\n }\\n\\n /// @notice Allows the Governance to mark the given vault address as trusted\\n /// or no longer trusted. Vaults are not trusted by default.\\n /// Trusted vault must meet the following criteria:\\n /// - `IVault.receiveBalanceIncrease` must have a known, low gas\\n /// cost,\\n /// - `IVault.receiveBalanceIncrease` must never revert.\\n /// @dev Without restricting reveal only to trusted vaults, malicious\\n /// vaults not meeting the criteria would be able to nuke sweep proof\\n /// transactions executed by ECDSA wallet with deposits routed to\\n /// them.\\n /// @param vault The address of the vault.\\n /// @param isTrusted flag indicating whether the vault is trusted or not.\\n /// @dev Can only be called by the Governance.\\n function setVaultStatus(address vault, bool isTrusted)\\n external\\n onlyGovernance\\n {\\n self.isVaultTrusted[vault] = isTrusted;\\n emit VaultStatusUpdated(vault, isTrusted);\\n }\\n\\n /// @notice Allows the Governance to mark the given address as trusted\\n /// or no longer trusted SPV maintainer. Addresses are not trusted\\n /// as SPV maintainers by default.\\n /// @dev The SPV proof does not check whether the transaction is a part of\\n /// the Bitcoin mainnet, it only checks whether the transaction has been\\n /// mined performing the required amount of work as on Bitcoin mainnet.\\n /// The possibility of submitting SPV proofs is limited to trusted SPV\\n /// maintainers. The system expects transaction confirmations with the\\n /// required work accumulated, so trusted SPV maintainers can not prove\\n /// the transaction without providing the required Bitcoin proof of work.\\n /// Trusted maintainers address the issue of an economic game between\\n /// tBTC and Bitcoin mainnet where large Bitcoin mining pools can decide\\n /// to use their hash power to mine fake Bitcoin blocks to prove them in\\n /// tBTC instead of receiving Bitcoin miner rewards.\\n /// @param spvMaintainer The address of the SPV maintainer.\\n /// @param isTrusted flag indicating whether the address is trusted or not.\\n /// @dev Can only be called by the Governance.\\n function setSpvMaintainerStatus(address spvMaintainer, bool isTrusted)\\n external\\n onlyGovernance\\n {\\n self.isSpvMaintainer[spvMaintainer] = isTrusted;\\n emit SpvMaintainerStatusUpdated(spvMaintainer, isTrusted);\\n }\\n\\n /// @notice Updates parameters of deposits.\\n /// @param depositDustThreshold New value of the deposit dust threshold in\\n /// satoshis. It is the minimal amount that can be requested to\\n //// deposit. Value of this parameter must take into account the value\\n /// of `depositTreasuryFeeDivisor` and `depositTxMaxFee` parameters\\n /// in order to make requests that can incur the treasury and\\n /// transaction fee and still satisfy the depositor.\\n /// @param depositTreasuryFeeDivisor New value of the treasury fee divisor.\\n /// It is the divisor used to compute the treasury fee taken from\\n /// each deposit and transferred to the treasury upon sweep proof\\n /// submission. That fee is computed as follows:\\n /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each deposit,\\n /// the `depositTreasuryFeeDivisor` should be set to `50`\\n /// because `1/50 = 0.02 = 2%`.\\n /// @param depositTxMaxFee New value of the deposit tx max fee in satoshis.\\n /// It is the maximum amount of BTC transaction fee that can\\n /// be incurred by each swept deposit being part of the given sweep\\n /// transaction. If the maximum BTC transaction fee is exceeded,\\n /// such transaction is considered a fraud.\\n /// @param depositRevealAheadPeriod New value of the deposit reveal ahead\\n /// period parameter in seconds. It defines the length of the period\\n /// that must be preserved between the deposit reveal time and the\\n /// deposit refund locktime.\\n /// @dev Requirements:\\n /// - Deposit dust threshold must be greater than zero,\\n /// - Deposit dust threshold must be greater than deposit TX max fee,\\n /// - Deposit transaction max fee must be greater than zero.\\n function updateDepositParameters(\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n ) external onlyGovernance {\\n self.updateDepositParameters(\\n depositDustThreshold,\\n depositTreasuryFeeDivisor,\\n depositTxMaxFee,\\n depositRevealAheadPeriod\\n );\\n }\\n\\n /// @notice Updates parameters of redemptions.\\n /// @param redemptionDustThreshold New value of the redemption dust\\n /// threshold in satoshis. It is the minimal amount that can be\\n /// requested for redemption. Value of this parameter must take into\\n /// account the value of `redemptionTreasuryFeeDivisor` and\\n /// `redemptionTxMaxFee` parameters in order to make requests that\\n /// can incur the treasury and transaction fee and still satisfy the\\n /// redeemer.\\n /// @param redemptionTreasuryFeeDivisor New value of the redemption\\n /// treasury fee divisor. It is the divisor used to compute the\\n /// treasury fee taken from each redemption request and transferred\\n /// to the treasury upon successful request finalization. That fee is\\n /// computed as follows:\\n /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each\\n /// redemption request, the `redemptionTreasuryFeeDivisor` should\\n /// be set to `50` because `1/50 = 0.02 = 2%`.\\n /// @param redemptionTxMaxFee New value of the redemption transaction max\\n /// fee in satoshis. It is the maximum amount of BTC transaction fee\\n /// that can be incurred by each redemption request being part of the\\n /// given redemption transaction. If the maximum BTC transaction fee\\n /// is exceeded, such transaction is considered a fraud.\\n /// This is a per-redemption output max fee for the redemption\\n /// transaction.\\n /// @param redemptionTxMaxTotalFee New value of the redemption transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single redemption\\n /// transaction. This is a _total_ max fee for the entire redemption\\n /// transaction.\\n /// @param redemptionTimeout New value of the redemption timeout in seconds.\\n /// It is the time after which the redemption request can be reported\\n /// as timed out. It is counted from the moment when the redemption\\n /// request was created via `requestRedemption` call. Reported timed\\n /// out requests are cancelled and locked balance is returned to the\\n /// redeemer in full amount.\\n /// @param redemptionTimeoutSlashingAmount New value of the redemption\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for redemption timeout.\\n /// @param redemptionTimeoutNotifierRewardMultiplier New value of the\\n /// redemption timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a redemption timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Redemption dust threshold must be greater than moving funds dust\\n /// threshold,\\n /// - Redemption dust threshold must be greater than the redemption TX\\n /// max fee,\\n /// - Redemption transaction max fee must be greater than zero,\\n /// - Redemption transaction max total fee must be greater than or\\n /// equal to the redemption transaction per-request max fee,\\n /// - Redemption timeout must be greater than zero,\\n /// - Redemption timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateRedemptionParameters(\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n ) external onlyGovernance {\\n self.updateRedemptionParameters(\\n redemptionDustThreshold,\\n redemptionTreasuryFeeDivisor,\\n redemptionTxMaxFee,\\n redemptionTxMaxTotalFee,\\n redemptionTimeout,\\n redemptionTimeoutSlashingAmount,\\n redemptionTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of moving funds.\\n /// @param movingFundsTxMaxTotalFee New value of the moving funds transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single moving funds\\n /// transaction. This is a _total_ max fee for the entire moving\\n /// funds transaction.\\n /// @param movingFundsDustThreshold New value of the moving funds dust\\n /// threshold. It is the minimal satoshi amount that makes sense to\\n /// be transferred during the moving funds process. Moving funds\\n /// wallets having their BTC balance below that value can begin\\n /// closing immediately as transferring such a low value may not be\\n /// possible due to BTC network fees.\\n /// @param movingFundsTimeoutResetDelay New value of the moving funds\\n /// timeout reset delay in seconds. It is the time after which the\\n /// moving funds timeout can be reset in case the target wallet\\n /// commitment cannot be submitted due to a lack of live wallets\\n /// in the system. It is counted from the moment when the wallet\\n /// was requested to move their funds and switched to the MovingFunds\\n /// state or from the moment the timeout was reset the last time.\\n /// @param movingFundsTimeout New value of the moving funds timeout in\\n /// seconds. It is the time after which the moving funds process can\\n /// be reported as timed out. It is counted from the moment when the\\n /// wallet was requested to move their funds and switched to the\\n /// MovingFunds state.\\n /// @param movingFundsTimeoutSlashingAmount New value of the moving funds\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for moving funds timeout.\\n /// @param movingFundsTimeoutNotifierRewardMultiplier New value of the\\n /// moving funds timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a moving funds timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @param movingFundsCommitmentGasOffset New value of the gas offset for\\n /// moving funds target wallet commitment transaction gas costs\\n /// reimbursement.\\n /// @param movedFundsSweepTxMaxTotalFee New value of the moved funds sweep\\n /// transaction max total fee in satoshis. It is the maximum amount\\n /// of the total BTC transaction fee that is acceptable in a single\\n /// moved funds sweep transaction. This is a _total_ max fee for the\\n /// entire moved funds sweep transaction.\\n /// @param movedFundsSweepTimeout New value of the moved funds sweep\\n /// timeout in seconds. It is the time after which the moved funds\\n /// sweep process can be reported as timed out. It is counted from\\n /// the moment when the wallet was requested to sweep the received\\n /// funds.\\n /// @param movedFundsSweepTimeoutSlashingAmount New value of the moved\\n /// funds sweep timeout slashing amount in T, it is the amount\\n /// slashed from each wallet member for moved funds sweep timeout.\\n /// @param movedFundsSweepTimeoutNotifierRewardMultiplier New value of\\n /// the moved funds sweep timeout notifier reward multiplier as\\n /// percentage, it determines the percentage of the notifier reward\\n /// from the staking contact the notifier of a moved funds sweep\\n /// timeout receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Moving funds transaction max total fee must be greater than zero,\\n /// - Moving funds dust threshold must be greater than zero and lower\\n /// than the redemption dust threshold,\\n /// - Moving funds timeout reset delay must be greater than zero,\\n /// - Moving funds timeout must be greater than the moving funds\\n /// timeout reset delay,\\n /// - Moving funds timeout notifier reward multiplier must be in the\\n /// range [0, 100],\\n /// - Moved funds sweep transaction max total fee must be greater than zero,\\n /// - Moved funds sweep timeout must be greater than zero,\\n /// - Moved funds sweep timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateMovingFundsParameters(\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n ) external onlyGovernance {\\n self.updateMovingFundsParameters(\\n movingFundsTxMaxTotalFee,\\n movingFundsDustThreshold,\\n movingFundsTimeoutResetDelay,\\n movingFundsTimeout,\\n movingFundsTimeoutSlashingAmount,\\n movingFundsTimeoutNotifierRewardMultiplier,\\n movingFundsCommitmentGasOffset,\\n movedFundsSweepTxMaxTotalFee,\\n movedFundsSweepTimeout,\\n movedFundsSweepTimeoutSlashingAmount,\\n movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of wallets.\\n /// @param walletCreationPeriod New value of the wallet creation period in\\n /// seconds, determines how frequently a new wallet creation can be\\n /// requested.\\n /// @param walletCreationMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param walletCreationMaxBtcBalance New value of the wallet maximum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param walletClosureMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet closure.\\n /// @param walletMaxAge New value of the wallet maximum age in seconds,\\n /// indicates the maximum age of a wallet in seconds, after which\\n /// the wallet moving funds process can be requested.\\n /// @param walletMaxBtcTransfer New value of the wallet maximum BTC transfer\\n /// in satoshi, determines the maximum amount that can be transferred\\n // to a single target wallet during the moving funds process.\\n /// @param walletClosingPeriod New value of the wallet closing period in\\n /// seconds, determines the length of the wallet closing period,\\n // i.e. the period when the wallet remains in the Closing state\\n // and can be subject of deposit fraud challenges.\\n /// @dev Requirements:\\n /// - Wallet maximum BTC balance must be greater than the wallet\\n /// minimum BTC balance,\\n /// - Wallet maximum BTC transfer must be greater than zero,\\n /// - Wallet closing period must be greater than zero.\\n function updateWalletParameters(\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n ) external onlyGovernance {\\n self.updateWalletParameters(\\n walletCreationPeriod,\\n walletCreationMinBtcBalance,\\n walletCreationMaxBtcBalance,\\n walletClosureMinBtcBalance,\\n walletMaxAge,\\n walletMaxBtcTransfer,\\n walletClosingPeriod\\n );\\n }\\n\\n /// @notice Updates parameters related to frauds.\\n /// @param fraudChallengeDepositAmount New value of the fraud challenge\\n /// deposit amount in wei, it is the amount of ETH the party\\n /// challenging the wallet for fraud needs to deposit.\\n /// @param fraudChallengeDefeatTimeout New value of the challenge defeat\\n /// timeout in seconds, it is the amount of time the wallet has to\\n /// defeat a fraud challenge. The value must be greater than zero.\\n /// @param fraudSlashingAmount New value of the fraud slashing amount in T,\\n /// it is the amount slashed from each wallet member for committing\\n /// a fraud.\\n /// @param fraudNotifierRewardMultiplier New value of the fraud notifier\\n /// reward multiplier as percentage, it determines the percentage of\\n /// the notifier reward from the staking contact the notifier of\\n /// a fraud receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Fraud challenge defeat timeout must be greater than 0,\\n /// - Fraud notifier reward multiplier must be in the range [0, 100].\\n function updateFraudParameters(\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n ) external onlyGovernance {\\n self.updateFraudParameters(\\n fraudChallengeDepositAmount,\\n fraudChallengeDefeatTimeout,\\n fraudSlashingAmount,\\n fraudNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates treasury address. The treasury receives the system fees.\\n /// @param treasury New value of the treasury address.\\n /// @dev The treasury address must not be 0x0.\\n // slither-disable-next-line shadowing-local\\n function updateTreasury(address treasury) external onlyGovernance {\\n self.updateTreasury(treasury);\\n }\\n\\n /// @notice Collection of all revealed deposits indexed by\\n /// keccak256(fundingTxHash | fundingOutputIndex).\\n /// The fundingTxHash is bytes32 (ordered as in Bitcoin internally)\\n /// and fundingOutputIndex an uint32. This mapping may contain valid\\n /// and invalid deposits and the wallet is responsible for\\n /// validating them before attempting to execute a sweep.\\n function deposits(uint256 depositKey)\\n external\\n view\\n returns (Deposit.DepositRequest memory)\\n {\\n return self.deposits[depositKey];\\n }\\n\\n /// @notice Collection of all pending redemption requests indexed by\\n /// redemption key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The walletPubKeyHash is the 20-byte wallet's public key hash\\n /// (computed using Bitcoin HASH160 over the compressed ECDSA\\n /// public key) and `redeemerOutputScript` is a Bitcoin script\\n /// (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC as requested by the redeemer. Requests are added\\n /// to this mapping by the `requestRedemption` method (duplicates\\n /// not allowed) and are removed by one of the following methods:\\n /// - `submitRedemptionProof` in case the request was handled\\n /// successfully,\\n /// - `notifyRedemptionTimeout` in case the request was reported\\n /// to be timed out.\\n function pendingRedemptions(uint256 redemptionKey)\\n external\\n view\\n returns (Redemption.RedemptionRequest memory)\\n {\\n return self.pendingRedemptions[redemptionKey];\\n }\\n\\n /// @notice Collection of all timed out redemptions requests indexed by\\n /// redemption key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The walletPubKeyHash is the 20-byte wallet's public key hash\\n /// (computed using Bitcoin HASH160 over the compressed ECDSA\\n /// public key) and `redeemerOutputScript` is the Bitcoin script\\n /// (P2PKH, P2WPKH, P2SH or P2WSH) that is involved in the timed\\n /// out request.\\n /// Only one method can add to this mapping:\\n /// - `notifyRedemptionTimeout` which puts the redemption key\\n /// to this mapping based on a timed out request stored\\n /// previously in `pendingRedemptions` mapping.\\n /// Only one method can remove entries from this mapping:\\n /// - `submitRedemptionProof` in case the timed out redemption\\n /// request was a part of the proven transaction.\\n function timedOutRedemptions(uint256 redemptionKey)\\n external\\n view\\n returns (Redemption.RedemptionRequest memory)\\n {\\n return self.timedOutRedemptions[redemptionKey];\\n }\\n\\n /// @notice Collection of main UTXOs that are honestly spent indexed by\\n /// keccak256(fundingTxHash | fundingOutputIndex). The fundingTxHash\\n /// is bytes32 (ordered as in Bitcoin internally) and\\n /// fundingOutputIndex an uint32. A main UTXO is considered honestly\\n /// spent if it was used as an input of a transaction that have been\\n /// proven in the Bridge.\\n function spentMainUTXOs(uint256 utxoKey) external view returns (bool) {\\n return self.spentMainUTXOs[utxoKey];\\n }\\n\\n /// @notice Gets details about a registered wallet.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @return Wallet details.\\n function wallets(bytes20 walletPubKeyHash)\\n external\\n view\\n returns (Wallets.Wallet memory)\\n {\\n return self.registeredWallets[walletPubKeyHash];\\n }\\n\\n /// @notice Gets the public key hash of the active wallet.\\n /// @return The 20-byte public key hash (computed using Bitcoin HASH160\\n /// over the compressed ECDSA public key) of the active wallet.\\n /// Returns bytes20(0) if there is no active wallet at the moment.\\n function activeWalletPubKeyHash() external view returns (bytes20) {\\n return self.activeWalletPubKeyHash;\\n }\\n\\n /// @notice Gets the live wallets count.\\n /// @return The current count of wallets being in the Live state.\\n function liveWalletsCount() external view returns (uint32) {\\n return self.liveWalletsCount;\\n }\\n\\n /// @notice Returns the fraud challenge identified by the given key built\\n /// as keccak256(walletPublicKey|sighash).\\n function fraudChallenges(uint256 challengeKey)\\n external\\n view\\n returns (Fraud.FraudChallenge memory)\\n {\\n return self.fraudChallenges[challengeKey];\\n }\\n\\n /// @notice Collection of all moved funds sweep requests indexed by\\n /// `keccak256(movingFundsTxHash | movingFundsOutputIndex)`.\\n /// The `movingFundsTxHash` is `bytes32` (ordered as in Bitcoin\\n /// internally) and `movingFundsOutputIndex` an `uint32`. Each entry\\n /// is actually an UTXO representing the moved funds and is supposed\\n /// to be swept with the current main UTXO of the recipient wallet.\\n /// @param requestKey Request key built as\\n /// `keccak256(movingFundsTxHash | movingFundsOutputIndex)`.\\n /// @return Details of the moved funds sweep request.\\n function movedFundsSweepRequests(uint256 requestKey)\\n external\\n view\\n returns (MovingFunds.MovedFundsSweepRequest memory)\\n {\\n return self.movedFundsSweepRequests[requestKey];\\n }\\n\\n /// @notice Indicates if the vault with the given address is trusted or not.\\n /// Depositors can route their revealed deposits only to trusted\\n /// vaults and have trusted vaults notified about new deposits as\\n /// soon as these deposits get swept. Vaults not trusted by the\\n /// Bridge can still be used by Bank balance owners on their own\\n /// responsibility - anyone can approve their Bank balance to any\\n /// address.\\n function isVaultTrusted(address vault) external view returns (bool) {\\n return self.isVaultTrusted[vault];\\n }\\n\\n /// @notice Returns the current values of Bridge deposit parameters.\\n /// @return depositDustThreshold The minimal amount that can be requested\\n /// to deposit. Value of this parameter must take into account the\\n /// value of `depositTreasuryFeeDivisor` and `depositTxMaxFee`\\n /// parameters in order to make requests that can incur the\\n /// treasury and transaction fee and still satisfy the depositor.\\n /// @return depositTreasuryFeeDivisor Divisor used to compute the treasury\\n /// fee taken from each deposit and transferred to the treasury upon\\n /// sweep proof submission. That fee is computed as follows:\\n /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each deposit,\\n /// the `depositTreasuryFeeDivisor` should be set to `50`\\n /// because `1/50 = 0.02 = 2%`.\\n /// @return depositTxMaxFee Maximum amount of BTC transaction fee that can\\n /// be incurred by each swept deposit being part of the given sweep\\n /// transaction. If the maximum BTC transaction fee is exceeded,\\n /// such transaction is considered a fraud.\\n /// @return depositRevealAheadPeriod Defines the length of the period that\\n /// must be preserved between the deposit reveal time and the\\n /// deposit refund locktime. For example, if the deposit become\\n /// refundable on August 1st, and the ahead period is 7 days, the\\n /// latest moment for deposit reveal is July 25th. Value in seconds.\\n function depositParameters()\\n external\\n view\\n returns (\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n )\\n {\\n depositDustThreshold = self.depositDustThreshold;\\n depositTreasuryFeeDivisor = self.depositTreasuryFeeDivisor;\\n depositTxMaxFee = self.depositTxMaxFee;\\n depositRevealAheadPeriod = self.depositRevealAheadPeriod;\\n }\\n\\n /// @notice Returns the current values of Bridge redemption parameters.\\n /// @return redemptionDustThreshold The minimal amount that can be requested\\n /// for redemption. Value of this parameter must take into account\\n /// the value of `redemptionTreasuryFeeDivisor` and `redemptionTxMaxFee`\\n /// parameters in order to make requests that can incur the\\n /// treasury and transaction fee and still satisfy the redeemer.\\n /// @return redemptionTreasuryFeeDivisor Divisor used to compute the treasury\\n /// fee taken from each redemption request and transferred to the\\n /// treasury upon successful request finalization. That fee is\\n /// computed as follows:\\n /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each\\n /// redemption request, the `redemptionTreasuryFeeDivisor` should\\n /// be set to `50` because `1/50 = 0.02 = 2%`.\\n /// @return redemptionTxMaxFee Maximum amount of BTC transaction fee that\\n /// can be incurred by each redemption request being part of the\\n /// given redemption transaction. If the maximum BTC transaction\\n /// fee is exceeded, such transaction is considered a fraud.\\n /// This is a per-redemption output max fee for the redemption\\n /// transaction.\\n /// @return redemptionTxMaxTotalFee Maximum amount of the total BTC\\n /// transaction fee that is acceptable in a single redemption\\n /// transaction. This is a _total_ max fee for the entire redemption\\n /// transaction.\\n /// @return redemptionTimeout Time after which the redemption request can be\\n /// reported as timed out. It is counted from the moment when the\\n /// redemption request was created via `requestRedemption` call.\\n /// Reported timed out requests are cancelled and locked balance is\\n /// returned to the redeemer in full amount.\\n /// @return redemptionTimeoutSlashingAmount The amount of stake slashed\\n /// from each member of a wallet for a redemption timeout.\\n /// @return redemptionTimeoutNotifierRewardMultiplier The percentage of the\\n /// notifier reward from the staking contract the notifier of a\\n /// redemption timeout receives. The value is in the range [0, 100].\\n function redemptionParameters()\\n external\\n view\\n returns (\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n )\\n {\\n redemptionDustThreshold = self.redemptionDustThreshold;\\n redemptionTreasuryFeeDivisor = self.redemptionTreasuryFeeDivisor;\\n redemptionTxMaxFee = self.redemptionTxMaxFee;\\n redemptionTxMaxTotalFee = self.redemptionTxMaxTotalFee;\\n redemptionTimeout = self.redemptionTimeout;\\n redemptionTimeoutSlashingAmount = self.redemptionTimeoutSlashingAmount;\\n redemptionTimeoutNotifierRewardMultiplier = self\\n .redemptionTimeoutNotifierRewardMultiplier;\\n }\\n\\n /// @notice Returns the current values of Bridge moving funds between\\n /// wallets parameters.\\n /// @return movingFundsTxMaxTotalFee Maximum amount of the total BTC\\n /// transaction fee that is acceptable in a single moving funds\\n /// transaction. This is a _total_ max fee for the entire moving\\n /// funds transaction.\\n /// @return movingFundsDustThreshold The minimal satoshi amount that makes\\n /// sense to be transferred during the moving funds process. Moving\\n /// funds wallets having their BTC balance below that value can\\n /// begin closing immediately as transferring such a low value may\\n /// not be possible due to BTC network fees.\\n /// @return movingFundsTimeoutResetDelay Time after which the moving funds\\n /// timeout can be reset in case the target wallet commitment\\n /// cannot be submitted due to a lack of live wallets in the system.\\n /// It is counted from the moment when the wallet was requested to\\n /// move their funds and switched to the MovingFunds state or from\\n /// the moment the timeout was reset the last time. Value in seconds\\n /// This value should be lower than the value of the\\n /// `movingFundsTimeout`.\\n /// @return movingFundsTimeout Time after which the moving funds process\\n /// can be reported as timed out. It is counted from the moment\\n /// when the wallet was requested to move their funds and switched\\n /// to the MovingFunds state. Value in seconds.\\n /// @return movingFundsTimeoutSlashingAmount The amount of stake slashed\\n /// from each member of a wallet for a moving funds timeout.\\n /// @return movingFundsTimeoutNotifierRewardMultiplier The percentage of the\\n /// notifier reward from the staking contract the notifier of a\\n /// moving funds timeout receives. The value is in the range [0, 100].\\n /// @return movingFundsCommitmentGasOffset The gas offset used for the\\n /// moving funds target wallet commitment transaction cost\\n /// reimbursement.\\n /// @return movedFundsSweepTxMaxTotalFee Maximum amount of the total BTC\\n /// transaction fee that is acceptable in a single moved funds\\n /// sweep transaction. This is a _total_ max fee for the entire\\n /// moved funds sweep transaction.\\n /// @return movedFundsSweepTimeout Time after which the moved funds sweep\\n /// process can be reported as timed out. It is counted from the\\n /// moment when the wallet was requested to sweep the received funds.\\n /// Value in seconds.\\n /// @return movedFundsSweepTimeoutSlashingAmount The amount of stake slashed\\n /// from each member of a wallet for a moved funds sweep timeout.\\n /// @return movedFundsSweepTimeoutNotifierRewardMultiplier The percentage\\n /// of the notifier reward from the staking contract the notifier\\n /// of a moved funds sweep timeout receives. The value is in the\\n /// range [0, 100].\\n function movingFundsParameters()\\n external\\n view\\n returns (\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n )\\n {\\n movingFundsTxMaxTotalFee = self.movingFundsTxMaxTotalFee;\\n movingFundsDustThreshold = self.movingFundsDustThreshold;\\n movingFundsTimeoutResetDelay = self.movingFundsTimeoutResetDelay;\\n movingFundsTimeout = self.movingFundsTimeout;\\n movingFundsTimeoutSlashingAmount = self\\n .movingFundsTimeoutSlashingAmount;\\n movingFundsTimeoutNotifierRewardMultiplier = self\\n .movingFundsTimeoutNotifierRewardMultiplier;\\n movingFundsCommitmentGasOffset = self.movingFundsCommitmentGasOffset;\\n movedFundsSweepTxMaxTotalFee = self.movedFundsSweepTxMaxTotalFee;\\n movedFundsSweepTimeout = self.movedFundsSweepTimeout;\\n movedFundsSweepTimeoutSlashingAmount = self\\n .movedFundsSweepTimeoutSlashingAmount;\\n movedFundsSweepTimeoutNotifierRewardMultiplier = self\\n .movedFundsSweepTimeoutNotifierRewardMultiplier;\\n }\\n\\n /// @return walletCreationPeriod Determines how frequently a new wallet\\n /// creation can be requested. Value in seconds.\\n /// @return walletCreationMinBtcBalance The minimum BTC threshold in satoshi\\n /// that is used to decide about wallet creation.\\n /// @return walletCreationMaxBtcBalance The maximum BTC threshold in satoshi\\n /// that is used to decide about wallet creation.\\n /// @return walletClosureMinBtcBalance The minimum BTC threshold in satoshi\\n /// that is used to decide about wallet closure.\\n /// @return walletMaxAge The maximum age of a wallet in seconds, after which\\n /// the wallet moving funds process can be requested.\\n /// @return walletMaxBtcTransfer The maximum BTC amount in satoshi than\\n /// can be transferred to a single target wallet during the moving\\n /// funds process.\\n /// @return walletClosingPeriod Determines the length of the wallet closing\\n /// period, i.e. the period when the wallet remains in the Closing\\n /// state and can be subject of deposit fraud challenges. Value\\n /// in seconds.\\n function walletParameters()\\n external\\n view\\n returns (\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n )\\n {\\n walletCreationPeriod = self.walletCreationPeriod;\\n walletCreationMinBtcBalance = self.walletCreationMinBtcBalance;\\n walletCreationMaxBtcBalance = self.walletCreationMaxBtcBalance;\\n walletClosureMinBtcBalance = self.walletClosureMinBtcBalance;\\n walletMaxAge = self.walletMaxAge;\\n walletMaxBtcTransfer = self.walletMaxBtcTransfer;\\n walletClosingPeriod = self.walletClosingPeriod;\\n }\\n\\n /// @notice Returns the current values of Bridge fraud parameters.\\n /// @return fraudChallengeDepositAmount The amount of ETH in wei the party\\n /// challenging the wallet for fraud needs to deposit.\\n /// @return fraudChallengeDefeatTimeout The amount of time the wallet has to\\n /// defeat a fraud challenge.\\n /// @return fraudSlashingAmount The amount slashed from each wallet member\\n /// for committing a fraud.\\n /// @return fraudNotifierRewardMultiplier The percentage of the notifier\\n /// reward from the staking contract the notifier of a fraud\\n /// receives. The value is in the range [0, 100].\\n function fraudParameters()\\n external\\n view\\n returns (\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n )\\n {\\n fraudChallengeDepositAmount = self.fraudChallengeDepositAmount;\\n fraudChallengeDefeatTimeout = self.fraudChallengeDefeatTimeout;\\n fraudSlashingAmount = self.fraudSlashingAmount;\\n fraudNotifierRewardMultiplier = self.fraudNotifierRewardMultiplier;\\n }\\n\\n /// @notice Returns the addresses of contracts Bridge is interacting with.\\n /// @return bank Address of the Bank the Bridge belongs to.\\n /// @return relay Address of the Bitcoin relay providing the current Bitcoin\\n /// network difficulty.\\n /// @return ecdsaWalletRegistry Address of the ECDSA Wallet Registry.\\n /// @return reimbursementPool Address of the Reimbursement Pool.\\n function contractReferences()\\n external\\n view\\n returns (\\n Bank bank,\\n IRelay relay,\\n EcdsaWalletRegistry ecdsaWalletRegistry,\\n ReimbursementPool reimbursementPool\\n )\\n {\\n bank = self.bank;\\n relay = self.relay;\\n ecdsaWalletRegistry = self.ecdsaWalletRegistry;\\n reimbursementPool = self.reimbursementPool;\\n }\\n\\n /// @notice Address where the deposit treasury fees will be sent to.\\n /// Treasury takes part in the operators rewarding process.\\n function treasury() external view returns (address) {\\n return self.treasury;\\n }\\n\\n /// @notice The number of confirmations on the Bitcoin chain required to\\n /// successfully evaluate an SPV proof.\\n function txProofDifficultyFactor() external view returns (uint256) {\\n return self.txProofDifficultyFactor;\\n }\\n}\\n\",\"keccak256\":\"0x0a6e8f890ba55fbd8671f4853c882e5c8c70e476d521013b21a30fa7d0f5bafd\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/BridgeState.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {IWalletRegistry as EcdsaWalletRegistry} from \\\"@keep-network/ecdsa/contracts/api/IWalletRegistry.sol\\\";\\nimport \\\"@keep-network/random-beacon/contracts/ReimbursementPool.sol\\\";\\n\\nimport \\\"./IRelay.sol\\\";\\nimport \\\"./Deposit.sol\\\";\\nimport \\\"./Redemption.sol\\\";\\nimport \\\"./Fraud.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\nimport \\\"./MovingFunds.sol\\\";\\n\\nimport \\\"../bank/Bank.sol\\\";\\n\\nlibrary BridgeState {\\n struct Storage {\\n // Address of the Bank the Bridge belongs to.\\n Bank bank;\\n // Bitcoin relay providing the current Bitcoin network difficulty.\\n IRelay relay;\\n // The number of confirmations on the Bitcoin chain required to\\n // successfully evaluate an SPV proof.\\n uint96 txProofDifficultyFactor;\\n // ECDSA Wallet Registry contract handle.\\n EcdsaWalletRegistry ecdsaWalletRegistry;\\n // Reimbursement Pool contract handle.\\n ReimbursementPool reimbursementPool;\\n // Address where the deposit and redemption treasury fees will be sent\\n // to. Treasury takes part in the operators rewarding process.\\n address treasury;\\n // Move depositDustThreshold to the next storage slot for a more\\n // efficient variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __treasuryAlignmentGap;\\n // The minimal amount that can be requested to deposit.\\n // Value of this parameter must take into account the value of\\n // `depositTreasuryFeeDivisor` and `depositTxMaxFee` parameters in order\\n // to make requests that can incur the treasury and transaction fee and\\n // still satisfy the depositor.\\n uint64 depositDustThreshold;\\n // Divisor used to compute the treasury fee taken from each deposit and\\n // transferred to the treasury upon sweep proof submission. That fee is\\n // computed as follows:\\n // `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n // For example, if the treasury fee needs to be 2% of each deposit,\\n // the `depositTreasuryFeeDivisor` should be set to `50` because\\n // `1/50 = 0.02 = 2%`.\\n uint64 depositTreasuryFeeDivisor;\\n // Maximum amount of BTC transaction fee that can be incurred by each\\n // swept deposit being part of the given sweep transaction. If the\\n // maximum BTC transaction fee is exceeded, such transaction is\\n // considered a fraud.\\n //\\n // This is a per-deposit input max fee for the sweep transaction.\\n uint64 depositTxMaxFee;\\n // Defines the length of the period that must be preserved between\\n // the deposit reveal time and the deposit refund locktime. For example,\\n // if the deposit become refundable on August 1st, and the ahead period\\n // is 7 days, the latest moment for deposit reveal is July 25th.\\n // Value in seconds. The value equal to zero disables the validation\\n // of this parameter.\\n uint32 depositRevealAheadPeriod;\\n // Move movingFundsTxMaxTotalFee to the next storage slot for a more\\n // efficient variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __depositAlignmentGap;\\n // Maximum amount of the total BTC transaction fee that is acceptable in\\n // a single moving funds transaction.\\n //\\n // This is a TOTAL max fee for the moving funds transaction. Note\\n // that `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`\\n // is per single redemption. `movingFundsTxMaxTotalFee` is a total\\n // fee for the entire transaction.\\n uint64 movingFundsTxMaxTotalFee;\\n // The minimal satoshi amount that makes sense to be transferred during\\n // the moving funds process. Moving funds wallets having their BTC\\n // balance below that value can begin closing immediately as\\n // transferring such a low value may not be possible due to\\n // BTC network fees. The value of this parameter must always be lower\\n // than `redemptionDustThreshold` in order to prevent redemption requests\\n // with values lower or equal to `movingFundsDustThreshold`.\\n uint64 movingFundsDustThreshold;\\n // Time after which the moving funds timeout can be reset in case the\\n // target wallet commitment cannot be submitted due to a lack of live\\n // wallets in the system. It is counted from the moment when the wallet\\n // was requested to move their funds and switched to the MovingFunds\\n // state or from the moment the timeout was reset the last time.\\n // Value in seconds. This value should be lower than the value\\n // of the `movingFundsTimeout`.\\n uint32 movingFundsTimeoutResetDelay;\\n // Time after which the moving funds process can be reported as\\n // timed out. It is counted from the moment when the wallet\\n // was requested to move their funds and switched to the MovingFunds\\n // state. Value in seconds.\\n uint32 movingFundsTimeout;\\n // The amount of stake slashed from each member of a wallet for a moving\\n // funds timeout.\\n uint96 movingFundsTimeoutSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a moving funds timeout receives. The value is in the\\n // range [0, 100].\\n uint32 movingFundsTimeoutNotifierRewardMultiplier;\\n // The gas offset used for the target wallet commitment transaction cost\\n // reimbursement.\\n uint16 movingFundsCommitmentGasOffset;\\n // Move movedFundsSweepTxMaxTotalFee to the next storage slot for a more\\n // efficient variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __movingFundsAlignmentGap;\\n // Maximum amount of the total BTC transaction fee that is acceptable in\\n // a single moved funds sweep transaction.\\n //\\n // This is a TOTAL max fee for the moved funds sweep transaction. Note\\n // that `depositTxMaxFee` is per single deposit and `redemptionTxMaxFee`\\n // is per single redemption. `movedFundsSweepTxMaxTotalFee` is a total\\n // fee for the entire transaction.\\n uint64 movedFundsSweepTxMaxTotalFee;\\n // Time after which the moved funds sweep process can be reported as\\n // timed out. It is counted from the moment when the recipient wallet\\n // was requested to sweep the received funds. Value in seconds.\\n uint32 movedFundsSweepTimeout;\\n // The amount of stake slashed from each member of a wallet for a moved\\n // funds sweep timeout.\\n uint96 movedFundsSweepTimeoutSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a moved funds sweep timeout receives. The value is\\n // in the range [0, 100].\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier;\\n // The minimal amount that can be requested for redemption.\\n // Value of this parameter must take into account the value of\\n // `redemptionTreasuryFeeDivisor` and `redemptionTxMaxFee`\\n // parameters in order to make requests that can incur the\\n // treasury and transaction fee and still satisfy the redeemer.\\n // Additionally, the value of this parameter must always be greater\\n // than `movingFundsDustThreshold` in order to prevent redemption\\n // requests with values lower or equal to `movingFundsDustThreshold`.\\n uint64 redemptionDustThreshold;\\n // Divisor used to compute the treasury fee taken from each\\n // redemption request and transferred to the treasury upon\\n // successful request finalization. That fee is computed as follows:\\n // `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n // For example, if the treasury fee needs to be 2% of each\\n // redemption request, the `redemptionTreasuryFeeDivisor` should\\n // be set to `50` because `1/50 = 0.02 = 2%`.\\n uint64 redemptionTreasuryFeeDivisor;\\n // Maximum amount of BTC transaction fee that can be incurred by\\n // each redemption request being part of the given redemption\\n // transaction. If the maximum BTC transaction fee is exceeded, such\\n // transaction is considered a fraud.\\n //\\n // This is a per-redemption output max fee for the redemption\\n // transaction.\\n uint64 redemptionTxMaxFee;\\n // Maximum amount of the total BTC transaction fee that is acceptable in\\n // a single redemption transaction.\\n //\\n // This is a TOTAL max fee for the redemption transaction. Note\\n // that the `redemptionTxMaxFee` is per single redemption.\\n // `redemptionTxMaxTotalFee` is a total fee for the entire transaction.\\n uint64 redemptionTxMaxTotalFee;\\n // Move redemptionTimeout to the next storage slot for a more efficient\\n // variable layout in the storage.\\n // slither-disable-next-line unused-state\\n bytes32 __redemptionAlignmentGap;\\n // Time after which the redemption request can be reported as\\n // timed out. It is counted from the moment when the redemption\\n // request was created via `requestRedemption` call. Reported\\n // timed out requests are cancelled and locked TBTC is returned\\n // to the redeemer in full amount.\\n uint32 redemptionTimeout;\\n // The amount of stake slashed from each member of a wallet for a\\n // redemption timeout.\\n uint96 redemptionTimeoutSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a redemption timeout receives. The value is in the\\n // range [0, 100].\\n uint32 redemptionTimeoutNotifierRewardMultiplier;\\n // The amount of ETH in wei the party challenging the wallet for fraud\\n // needs to deposit.\\n uint96 fraudChallengeDepositAmount;\\n // The amount of time the wallet has to defeat a fraud challenge.\\n uint32 fraudChallengeDefeatTimeout;\\n // The amount of stake slashed from each member of a wallet for a fraud.\\n uint96 fraudSlashingAmount;\\n // The percentage of the notifier reward from the staking contract\\n // the notifier of a fraud receives. The value is in the range [0, 100].\\n uint32 fraudNotifierRewardMultiplier;\\n // Determines how frequently a new wallet creation can be requested.\\n // Value in seconds.\\n uint32 walletCreationPeriod;\\n // The minimum BTC threshold in satoshi that is used to decide about\\n // wallet creation. Specifically, we allow for the creation of a new\\n // wallet if the active wallet is old enough and their amount of BTC\\n // is greater than or equal this threshold.\\n uint64 walletCreationMinBtcBalance;\\n // The maximum BTC threshold in satoshi that is used to decide about\\n // wallet creation. Specifically, we allow for the creation of a new\\n // wallet if the active wallet's amount of BTC is greater than or equal\\n // this threshold, regardless of the active wallet's age.\\n uint64 walletCreationMaxBtcBalance;\\n // The minimum BTC threshold in satoshi that is used to decide about\\n // wallet closing. Specifically, we allow for the closure of the given\\n // wallet if their amount of BTC is lesser than this threshold,\\n // regardless of the wallet's age.\\n uint64 walletClosureMinBtcBalance;\\n // The maximum age of a wallet in seconds, after which the wallet\\n // moving funds process can be requested.\\n uint32 walletMaxAge;\\n // 20-byte wallet public key hash being reference to the currently\\n // active wallet. Can be unset to the zero value under certain\\n // circumstances.\\n bytes20 activeWalletPubKeyHash;\\n // The current number of wallets in the Live state.\\n uint32 liveWalletsCount;\\n // The maximum BTC amount in satoshi than can be transferred to a single\\n // target wallet during the moving funds process.\\n uint64 walletMaxBtcTransfer;\\n // Determines the length of the wallet closing period, i.e. the period\\n // when the wallet remains in the Closing state and can be subject\\n // of deposit fraud challenges. This value is in seconds and should be\\n // greater than the deposit refund time plus some time margin.\\n uint32 walletClosingPeriod;\\n // Collection of all revealed deposits indexed by\\n // `keccak256(fundingTxHash | fundingOutputIndex)`.\\n // The `fundingTxHash` is `bytes32` (ordered as in Bitcoin internally)\\n // and `fundingOutputIndex` an `uint32`. This mapping may contain valid\\n // and invalid deposits and the wallet is responsible for validating\\n // them before attempting to execute a sweep.\\n mapping(uint256 => Deposit.DepositRequest) deposits;\\n // Indicates if the vault with the given address is trusted.\\n // Depositors can route their revealed deposits only to trusted vaults\\n // and have trusted vaults notified about new deposits as soon as these\\n // deposits get swept. Vaults not trusted by the Bridge can still be\\n // used by Bank balance owners on their own responsibility - anyone can\\n // approve their Bank balance to any address.\\n mapping(address => bool) isVaultTrusted;\\n // Indicates if the address is a trusted SPV maintainer.\\n // The SPV proof does not check whether the transaction is a part of the\\n // Bitcoin mainnet, it only checks whether the transaction has been\\n // mined performing the required amount of work as on Bitcoin mainnet.\\n // The possibility of submitting SPV proofs is limited to trusted SPV\\n // maintainers. The system expects transaction confirmations with the\\n // required work accumulated, so trusted SPV maintainers can not prove\\n // the transaction without providing the required Bitcoin proof of work.\\n // Trusted maintainers address the issue of an economic game between\\n // tBTC and Bitcoin mainnet where large Bitcoin mining pools can decide\\n // to use their hash power to mine fake Bitcoin blocks to prove them in\\n // tBTC instead of receiving Bitcoin miner rewards.\\n mapping(address => bool) isSpvMaintainer;\\n // Collection of all moved funds sweep requests indexed by\\n // `keccak256(movingFundsTxHash | movingFundsOutputIndex)`.\\n // The `movingFundsTxHash` is `bytes32` (ordered as in Bitcoin\\n // internally) and `movingFundsOutputIndex` an `uint32`. Each entry\\n // is actually an UTXO representing the moved funds and is supposed\\n // to be swept with the current main UTXO of the recipient wallet.\\n mapping(uint256 => MovingFunds.MovedFundsSweepRequest) movedFundsSweepRequests;\\n // Collection of all pending redemption requests indexed by\\n // redemption key built as\\n // `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n // The `walletPubKeyHash` is the 20-byte wallet's public key hash\\n // (computed using Bitcoin HASH160 over the compressed ECDSA\\n // public key) and `redeemerOutputScript` is a Bitcoin script\\n // (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n // redeemed BTC as requested by the redeemer. Requests are added\\n // to this mapping by the `requestRedemption` method (duplicates\\n // not allowed) and are removed by one of the following methods:\\n // - `submitRedemptionProof` in case the request was handled\\n // successfully,\\n // - `notifyRedemptionTimeout` in case the request was reported\\n // to be timed out.\\n mapping(uint256 => Redemption.RedemptionRequest) pendingRedemptions;\\n // Collection of all timed out redemptions requests indexed by\\n // redemption key built as\\n // `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n // The `walletPubKeyHash` is the 20-byte wallet's public key hash\\n // (computed using Bitcoin HASH160 over the compressed ECDSA\\n // public key) and `redeemerOutputScript` is the Bitcoin script\\n // (P2PKH, P2WPKH, P2SH or P2WSH) that is involved in the timed\\n // out request.\\n // Only one method can add to this mapping:\\n // - `notifyRedemptionTimeout` which puts the redemption key to this\\n // mapping based on a timed out request stored previously in\\n // `pendingRedemptions` mapping.\\n // Only one method can remove entries from this mapping:\\n // - `submitRedemptionProof` in case the timed out redemption request\\n // was a part of the proven transaction.\\n mapping(uint256 => Redemption.RedemptionRequest) timedOutRedemptions;\\n // Collection of all submitted fraud challenges indexed by challenge\\n // key built as `keccak256(walletPublicKey|sighash)`.\\n mapping(uint256 => Fraud.FraudChallenge) fraudChallenges;\\n // Collection of main UTXOs that are honestly spent indexed by\\n // `keccak256(fundingTxHash | fundingOutputIndex)`. The `fundingTxHash`\\n // is `bytes32` (ordered as in Bitcoin internally) and\\n // `fundingOutputIndex` an `uint32`. A main UTXO is considered honestly\\n // spent if it was used as an input of a transaction that have been\\n // proven in the Bridge.\\n mapping(uint256 => bool) spentMainUTXOs;\\n // Maps the 20-byte wallet public key hash (computed using Bitcoin\\n // HASH160 over the compressed ECDSA public key) to the basic wallet\\n // information like state and pending redemptions value.\\n mapping(bytes20 => Wallets.Wallet) registeredWallets;\\n // Reserved storage space in case we need to add more variables.\\n // The convention from OpenZeppelin suggests the storage space should\\n // add up to 50 slots. Here we want to have more slots as there are\\n // planned upgrades of the Bridge contract. If more entires are added to\\n // the struct in the upcoming versions we need to reduce the array size.\\n // See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\\n // slither-disable-next-line unused-state\\n uint256[50] __gap;\\n }\\n\\n event DepositParametersUpdated(\\n uint64 depositDustThreshold,\\n uint64 depositTreasuryFeeDivisor,\\n uint64 depositTxMaxFee,\\n uint32 depositRevealAheadPeriod\\n );\\n\\n event RedemptionParametersUpdated(\\n uint64 redemptionDustThreshold,\\n uint64 redemptionTreasuryFeeDivisor,\\n uint64 redemptionTxMaxFee,\\n uint64 redemptionTxMaxTotalFee,\\n uint32 redemptionTimeout,\\n uint96 redemptionTimeoutSlashingAmount,\\n uint32 redemptionTimeoutNotifierRewardMultiplier\\n );\\n\\n event MovingFundsParametersUpdated(\\n uint64 movingFundsTxMaxTotalFee,\\n uint64 movingFundsDustThreshold,\\n uint32 movingFundsTimeoutResetDelay,\\n uint32 movingFundsTimeout,\\n uint96 movingFundsTimeoutSlashingAmount,\\n uint32 movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 movingFundsCommitmentGasOffset,\\n uint64 movedFundsSweepTxMaxTotalFee,\\n uint32 movedFundsSweepTimeout,\\n uint96 movedFundsSweepTimeoutSlashingAmount,\\n uint32 movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n\\n event WalletParametersUpdated(\\n uint32 walletCreationPeriod,\\n uint64 walletCreationMinBtcBalance,\\n uint64 walletCreationMaxBtcBalance,\\n uint64 walletClosureMinBtcBalance,\\n uint32 walletMaxAge,\\n uint64 walletMaxBtcTransfer,\\n uint32 walletClosingPeriod\\n );\\n\\n event FraudParametersUpdated(\\n uint96 fraudChallengeDepositAmount,\\n uint32 fraudChallengeDefeatTimeout,\\n uint96 fraudSlashingAmount,\\n uint32 fraudNotifierRewardMultiplier\\n );\\n\\n event TreasuryUpdated(address treasury);\\n\\n /// @notice Updates parameters of deposits.\\n /// @param _depositDustThreshold New value of the deposit dust threshold in\\n /// satoshis. It is the minimal amount that can be requested to\\n //// deposit. Value of this parameter must take into account the value\\n /// of `depositTreasuryFeeDivisor` and `depositTxMaxFee` parameters\\n /// in order to make requests that can incur the treasury and\\n /// transaction fee and still satisfy the depositor.\\n /// @param _depositTreasuryFeeDivisor New value of the treasury fee divisor.\\n /// It is the divisor used to compute the treasury fee taken from\\n /// each deposit and transferred to the treasury upon sweep proof\\n /// submission. That fee is computed as follows:\\n /// `treasuryFee = depositedAmount / depositTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each deposit,\\n /// the `depositTreasuryFeeDivisor` should be set to `50`\\n /// because `1/50 = 0.02 = 2%`.\\n /// @param _depositTxMaxFee New value of the deposit tx max fee in satoshis.\\n /// It is the maximum amount of BTC transaction fee that can\\n /// be incurred by each swept deposit being part of the given sweep\\n /// transaction. If the maximum BTC transaction fee is exceeded,\\n /// such transaction is considered a fraud.\\n /// @param _depositRevealAheadPeriod New value of the deposit reveal ahead\\n /// period parameter in seconds. It defines the length of the period\\n /// that must be preserved between the deposit reveal time and the\\n /// deposit refund locktime.\\n /// @dev Requirements:\\n /// - Deposit dust threshold must be greater than zero,\\n /// - Deposit dust threshold must be greater than deposit TX max fee,\\n /// - Deposit transaction max fee must be greater than zero.\\n function updateDepositParameters(\\n Storage storage self,\\n uint64 _depositDustThreshold,\\n uint64 _depositTreasuryFeeDivisor,\\n uint64 _depositTxMaxFee,\\n uint32 _depositRevealAheadPeriod\\n ) internal {\\n require(\\n _depositDustThreshold > 0,\\n \\\"Deposit dust threshold must be greater than zero\\\"\\n );\\n\\n require(\\n _depositDustThreshold > _depositTxMaxFee,\\n \\\"Deposit dust threshold must be greater than deposit TX max fee\\\"\\n );\\n\\n require(\\n _depositTxMaxFee > 0,\\n \\\"Deposit transaction max fee must be greater than zero\\\"\\n );\\n\\n self.depositDustThreshold = _depositDustThreshold;\\n self.depositTreasuryFeeDivisor = _depositTreasuryFeeDivisor;\\n self.depositTxMaxFee = _depositTxMaxFee;\\n self.depositRevealAheadPeriod = _depositRevealAheadPeriod;\\n\\n emit DepositParametersUpdated(\\n _depositDustThreshold,\\n _depositTreasuryFeeDivisor,\\n _depositTxMaxFee,\\n _depositRevealAheadPeriod\\n );\\n }\\n\\n /// @notice Updates parameters of redemptions.\\n /// @param _redemptionDustThreshold New value of the redemption dust\\n /// threshold in satoshis. It is the minimal amount that can be\\n /// requested for redemption. Value of this parameter must take into\\n /// account the value of `redemptionTreasuryFeeDivisor` and\\n /// `redemptionTxMaxFee` parameters in order to make requests that\\n /// can incur the treasury and transaction fee and still satisfy the\\n /// redeemer.\\n /// @param _redemptionTreasuryFeeDivisor New value of the redemption\\n /// treasury fee divisor. It is the divisor used to compute the\\n /// treasury fee taken from each redemption request and transferred\\n /// to the treasury upon successful request finalization. That fee is\\n /// computed as follows:\\n /// `treasuryFee = requestedAmount / redemptionTreasuryFeeDivisor`\\n /// For example, if the treasury fee needs to be 2% of each\\n /// redemption request, the `redemptionTreasuryFeeDivisor` should\\n /// be set to `50` because `1/50 = 0.02 = 2%`.\\n /// @param _redemptionTxMaxFee New value of the redemption transaction max\\n /// fee in satoshis. It is the maximum amount of BTC transaction fee\\n /// that can be incurred by each redemption request being part of the\\n /// given redemption transaction. If the maximum BTC transaction fee\\n /// is exceeded, such transaction is considered a fraud.\\n /// This is a per-redemption output max fee for the redemption\\n /// transaction.\\n /// @param _redemptionTxMaxTotalFee New value of the redemption transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single redemption\\n /// transaction. This is a _total_ max fee for the entire redemption\\n /// transaction.\\n /// @param _redemptionTimeout New value of the redemption timeout in seconds.\\n /// It is the time after which the redemption request can be reported\\n /// as timed out. It is counted from the moment when the redemption\\n /// request was created via `requestRedemption` call. Reported timed\\n /// out requests are cancelled and locked TBTC is returned to the\\n /// redeemer in full amount.\\n /// @param _redemptionTimeoutSlashingAmount New value of the redemption\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for redemption timeout.\\n /// @param _redemptionTimeoutNotifierRewardMultiplier New value of the\\n /// redemption timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a redemption timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Redemption dust threshold must be greater than moving funds dust\\n /// threshold,\\n /// - Redemption dust threshold must be greater than the redemption TX\\n /// max fee,\\n /// - Redemption transaction max fee must be greater than zero,\\n /// - Redemption transaction max total fee must be greater than or\\n /// equal to the redemption transaction per-request max fee,\\n /// - Redemption timeout must be greater than zero,\\n /// - Redemption timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateRedemptionParameters(\\n Storage storage self,\\n uint64 _redemptionDustThreshold,\\n uint64 _redemptionTreasuryFeeDivisor,\\n uint64 _redemptionTxMaxFee,\\n uint64 _redemptionTxMaxTotalFee,\\n uint32 _redemptionTimeout,\\n uint96 _redemptionTimeoutSlashingAmount,\\n uint32 _redemptionTimeoutNotifierRewardMultiplier\\n ) internal {\\n require(\\n _redemptionDustThreshold > self.movingFundsDustThreshold,\\n \\\"Redemption dust threshold must be greater than moving funds dust threshold\\\"\\n );\\n\\n require(\\n _redemptionDustThreshold > _redemptionTxMaxFee,\\n \\\"Redemption dust threshold must be greater than redemption TX max fee\\\"\\n );\\n\\n require(\\n _redemptionTxMaxFee > 0,\\n \\\"Redemption transaction max fee must be greater than zero\\\"\\n );\\n\\n require(\\n _redemptionTxMaxTotalFee >= _redemptionTxMaxFee,\\n \\\"Redemption transaction max total fee must be greater than or equal to the redemption transaction per-request max fee\\\"\\n );\\n\\n require(\\n _redemptionTimeout > 0,\\n \\\"Redemption timeout must be greater than zero\\\"\\n );\\n\\n require(\\n _redemptionTimeoutNotifierRewardMultiplier <= 100,\\n \\\"Redemption timeout notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n self.redemptionDustThreshold = _redemptionDustThreshold;\\n self.redemptionTreasuryFeeDivisor = _redemptionTreasuryFeeDivisor;\\n self.redemptionTxMaxFee = _redemptionTxMaxFee;\\n self.redemptionTxMaxTotalFee = _redemptionTxMaxTotalFee;\\n self.redemptionTimeout = _redemptionTimeout;\\n self.redemptionTimeoutSlashingAmount = _redemptionTimeoutSlashingAmount;\\n self\\n .redemptionTimeoutNotifierRewardMultiplier = _redemptionTimeoutNotifierRewardMultiplier;\\n\\n emit RedemptionParametersUpdated(\\n _redemptionDustThreshold,\\n _redemptionTreasuryFeeDivisor,\\n _redemptionTxMaxFee,\\n _redemptionTxMaxTotalFee,\\n _redemptionTimeout,\\n _redemptionTimeoutSlashingAmount,\\n _redemptionTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of moving funds.\\n /// @param _movingFundsTxMaxTotalFee New value of the moving funds transaction\\n /// max total fee in satoshis. It is the maximum amount of the total\\n /// BTC transaction fee that is acceptable in a single moving funds\\n /// transaction. This is a _total_ max fee for the entire moving\\n /// funds transaction.\\n /// @param _movingFundsDustThreshold New value of the moving funds dust\\n /// threshold. It is the minimal satoshi amount that makes sense to\\n /// be transferred during the moving funds process. Moving funds\\n /// wallets having their BTC balance below that value can begin\\n /// closing immediately as transferring such a low value may not be\\n /// possible due to BTC network fees.\\n /// @param _movingFundsTimeoutResetDelay New value of the moving funds\\n /// timeout reset delay in seconds. It is the time after which the\\n /// moving funds timeout can be reset in case the target wallet\\n /// commitment cannot be submitted due to a lack of live wallets\\n /// in the system. It is counted from the moment when the wallet\\n /// was requested to move their funds and switched to the MovingFunds\\n /// state or from the moment the timeout was reset the last time.\\n /// @param _movingFundsTimeout New value of the moving funds timeout in\\n /// seconds. It is the time after which the moving funds process can\\n /// be reported as timed out. It is counted from the moment when the\\n /// wallet was requested to move their funds and switched to the\\n /// MovingFunds state.\\n /// @param _movingFundsTimeoutSlashingAmount New value of the moving funds\\n /// timeout slashing amount in T, it is the amount slashed from each\\n /// wallet member for moving funds timeout.\\n /// @param _movingFundsTimeoutNotifierRewardMultiplier New value of the\\n /// moving funds timeout notifier reward multiplier as percentage,\\n /// it determines the percentage of the notifier reward from the\\n /// staking contact the notifier of a moving funds timeout receives.\\n /// The value must be in the range [0, 100].\\n /// @param _movingFundsCommitmentGasOffset New value of the gas offset for\\n /// moving funds target wallet commitment transaction gas costs\\n /// reimbursement.\\n /// @param _movedFundsSweepTxMaxTotalFee New value of the moved funds sweep\\n /// transaction max total fee in satoshis. It is the maximum amount\\n /// of the total BTC transaction fee that is acceptable in a single\\n /// moved funds sweep transaction. This is a _total_ max fee for the\\n /// entire moved funds sweep transaction.\\n /// @param _movedFundsSweepTimeout New value of the moved funds sweep\\n /// timeout in seconds. It is the time after which the moved funds\\n /// sweep process can be reported as timed out. It is counted from\\n /// the moment when the wallet was requested to sweep the received\\n /// funds.\\n /// @param _movedFundsSweepTimeoutSlashingAmount New value of the moved\\n /// funds sweep timeout slashing amount in T, it is the amount\\n /// slashed from each wallet member for moved funds sweep timeout.\\n /// @param _movedFundsSweepTimeoutNotifierRewardMultiplier New value of\\n /// the moved funds sweep timeout notifier reward multiplier as\\n /// percentage, it determines the percentage of the notifier reward\\n /// from the staking contact the notifier of a moved funds sweep\\n /// timeout receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Moving funds transaction max total fee must be greater than zero,\\n /// - Moving funds dust threshold must be greater than zero and lower\\n /// than the redemption dust threshold,\\n /// - Moving funds timeout reset delay must be greater than zero,\\n /// - Moving funds timeout must be greater than the moving funds\\n /// timeout reset delay,\\n /// - Moving funds timeout notifier reward multiplier must be in the\\n /// range [0, 100],\\n /// - Moved funds sweep transaction max total fee must be greater than zero,\\n /// - Moved funds sweep timeout must be greater than zero,\\n /// - Moved funds sweep timeout notifier reward multiplier must be in the\\n /// range [0, 100].\\n function updateMovingFundsParameters(\\n Storage storage self,\\n uint64 _movingFundsTxMaxTotalFee,\\n uint64 _movingFundsDustThreshold,\\n uint32 _movingFundsTimeoutResetDelay,\\n uint32 _movingFundsTimeout,\\n uint96 _movingFundsTimeoutSlashingAmount,\\n uint32 _movingFundsTimeoutNotifierRewardMultiplier,\\n uint16 _movingFundsCommitmentGasOffset,\\n uint64 _movedFundsSweepTxMaxTotalFee,\\n uint32 _movedFundsSweepTimeout,\\n uint96 _movedFundsSweepTimeoutSlashingAmount,\\n uint32 _movedFundsSweepTimeoutNotifierRewardMultiplier\\n ) internal {\\n require(\\n _movingFundsTxMaxTotalFee > 0,\\n \\\"Moving funds transaction max total fee must be greater than zero\\\"\\n );\\n\\n require(\\n _movingFundsDustThreshold > 0 &&\\n _movingFundsDustThreshold < self.redemptionDustThreshold,\\n \\\"Moving funds dust threshold must be greater than zero and lower than redemption dust threshold\\\"\\n );\\n\\n require(\\n _movingFundsTimeoutResetDelay > 0,\\n \\\"Moving funds timeout reset delay must be greater than zero\\\"\\n );\\n\\n require(\\n _movingFundsTimeout > _movingFundsTimeoutResetDelay,\\n \\\"Moving funds timeout must be greater than its reset delay\\\"\\n );\\n\\n require(\\n _movingFundsTimeoutNotifierRewardMultiplier <= 100,\\n \\\"Moving funds timeout notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n require(\\n _movedFundsSweepTxMaxTotalFee > 0,\\n \\\"Moved funds sweep transaction max total fee must be greater than zero\\\"\\n );\\n\\n require(\\n _movedFundsSweepTimeout > 0,\\n \\\"Moved funds sweep timeout must be greater than zero\\\"\\n );\\n\\n require(\\n _movedFundsSweepTimeoutNotifierRewardMultiplier <= 100,\\n \\\"Moved funds sweep timeout notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n self.movingFundsTxMaxTotalFee = _movingFundsTxMaxTotalFee;\\n self.movingFundsDustThreshold = _movingFundsDustThreshold;\\n self.movingFundsTimeoutResetDelay = _movingFundsTimeoutResetDelay;\\n self.movingFundsTimeout = _movingFundsTimeout;\\n self\\n .movingFundsTimeoutSlashingAmount = _movingFundsTimeoutSlashingAmount;\\n self\\n .movingFundsTimeoutNotifierRewardMultiplier = _movingFundsTimeoutNotifierRewardMultiplier;\\n self.movingFundsCommitmentGasOffset = _movingFundsCommitmentGasOffset;\\n self.movedFundsSweepTxMaxTotalFee = _movedFundsSweepTxMaxTotalFee;\\n self.movedFundsSweepTimeout = _movedFundsSweepTimeout;\\n self\\n .movedFundsSweepTimeoutSlashingAmount = _movedFundsSweepTimeoutSlashingAmount;\\n self\\n .movedFundsSweepTimeoutNotifierRewardMultiplier = _movedFundsSweepTimeoutNotifierRewardMultiplier;\\n\\n emit MovingFundsParametersUpdated(\\n _movingFundsTxMaxTotalFee,\\n _movingFundsDustThreshold,\\n _movingFundsTimeoutResetDelay,\\n _movingFundsTimeout,\\n _movingFundsTimeoutSlashingAmount,\\n _movingFundsTimeoutNotifierRewardMultiplier,\\n _movingFundsCommitmentGasOffset,\\n _movedFundsSweepTxMaxTotalFee,\\n _movedFundsSweepTimeout,\\n _movedFundsSweepTimeoutSlashingAmount,\\n _movedFundsSweepTimeoutNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates parameters of wallets.\\n /// @param _walletCreationPeriod New value of the wallet creation period in\\n /// seconds, determines how frequently a new wallet creation can be\\n /// requested.\\n /// @param _walletCreationMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param _walletCreationMaxBtcBalance New value of the wallet maximum BTC\\n /// balance in satoshi, used to decide about wallet creation.\\n /// @param _walletClosureMinBtcBalance New value of the wallet minimum BTC\\n /// balance in satoshi, used to decide about wallet closure.\\n /// @param _walletMaxAge New value of the wallet maximum age in seconds,\\n /// indicates the maximum age of a wallet in seconds, after which\\n /// the wallet moving funds process can be requested.\\n /// @param _walletMaxBtcTransfer New value of the wallet maximum BTC transfer\\n /// in satoshi, determines the maximum amount that can be transferred\\n /// to a single target wallet during the moving funds process.\\n /// @param _walletClosingPeriod New value of the wallet closing period in\\n /// seconds, determines the length of the wallet closing period,\\n // i.e. the period when the wallet remains in the Closing state\\n // and can be subject of deposit fraud challenges.\\n /// @dev Requirements:\\n /// - Wallet maximum BTC balance must be greater than the wallet\\n /// minimum BTC balance,\\n /// - Wallet maximum BTC transfer must be greater than zero,\\n /// - Wallet closing period must be greater than zero.\\n function updateWalletParameters(\\n Storage storage self,\\n uint32 _walletCreationPeriod,\\n uint64 _walletCreationMinBtcBalance,\\n uint64 _walletCreationMaxBtcBalance,\\n uint64 _walletClosureMinBtcBalance,\\n uint32 _walletMaxAge,\\n uint64 _walletMaxBtcTransfer,\\n uint32 _walletClosingPeriod\\n ) internal {\\n require(\\n _walletCreationMaxBtcBalance > _walletCreationMinBtcBalance,\\n \\\"Wallet creation maximum BTC balance must be greater than the creation minimum BTC balance\\\"\\n );\\n require(\\n _walletMaxBtcTransfer > 0,\\n \\\"Wallet maximum BTC transfer must be greater than zero\\\"\\n );\\n require(\\n _walletClosingPeriod > 0,\\n \\\"Wallet closing period must be greater than zero\\\"\\n );\\n\\n self.walletCreationPeriod = _walletCreationPeriod;\\n self.walletCreationMinBtcBalance = _walletCreationMinBtcBalance;\\n self.walletCreationMaxBtcBalance = _walletCreationMaxBtcBalance;\\n self.walletClosureMinBtcBalance = _walletClosureMinBtcBalance;\\n self.walletMaxAge = _walletMaxAge;\\n self.walletMaxBtcTransfer = _walletMaxBtcTransfer;\\n self.walletClosingPeriod = _walletClosingPeriod;\\n\\n emit WalletParametersUpdated(\\n _walletCreationPeriod,\\n _walletCreationMinBtcBalance,\\n _walletCreationMaxBtcBalance,\\n _walletClosureMinBtcBalance,\\n _walletMaxAge,\\n _walletMaxBtcTransfer,\\n _walletClosingPeriod\\n );\\n }\\n\\n /// @notice Updates parameters related to frauds.\\n /// @param _fraudChallengeDepositAmount New value of the fraud challenge\\n /// deposit amount in wei, it is the amount of ETH the party\\n /// challenging the wallet for fraud needs to deposit.\\n /// @param _fraudChallengeDefeatTimeout New value of the challenge defeat\\n /// timeout in seconds, it is the amount of time the wallet has to\\n /// defeat a fraud challenge. The value must be greater than zero.\\n /// @param _fraudSlashingAmount New value of the fraud slashing amount in T,\\n /// it is the amount slashed from each wallet member for committing\\n /// a fraud.\\n /// @param _fraudNotifierRewardMultiplier New value of the fraud notifier\\n /// reward multiplier as percentage, it determines the percentage of\\n /// the notifier reward from the staking contact the notifier of\\n /// a fraud receives. The value must be in the range [0, 100].\\n /// @dev Requirements:\\n /// - Fraud challenge defeat timeout must be greater than 0,\\n /// - Fraud notifier reward multiplier must be in the range [0, 100].\\n function updateFraudParameters(\\n Storage storage self,\\n uint96 _fraudChallengeDepositAmount,\\n uint32 _fraudChallengeDefeatTimeout,\\n uint96 _fraudSlashingAmount,\\n uint32 _fraudNotifierRewardMultiplier\\n ) internal {\\n require(\\n _fraudChallengeDefeatTimeout > 0,\\n \\\"Fraud challenge defeat timeout must be greater than zero\\\"\\n );\\n\\n require(\\n _fraudNotifierRewardMultiplier <= 100,\\n \\\"Fraud notifier reward multiplier must be in the range [0, 100]\\\"\\n );\\n\\n self.fraudChallengeDepositAmount = _fraudChallengeDepositAmount;\\n self.fraudChallengeDefeatTimeout = _fraudChallengeDefeatTimeout;\\n self.fraudSlashingAmount = _fraudSlashingAmount;\\n self.fraudNotifierRewardMultiplier = _fraudNotifierRewardMultiplier;\\n\\n emit FraudParametersUpdated(\\n _fraudChallengeDepositAmount,\\n _fraudChallengeDefeatTimeout,\\n _fraudSlashingAmount,\\n _fraudNotifierRewardMultiplier\\n );\\n }\\n\\n /// @notice Updates treasury address. The treasury receives the system fees.\\n /// @param _treasury New value of the treasury address.\\n /// @dev The treasury address must not be 0x0.\\n function updateTreasury(Storage storage self, address _treasury) internal {\\n require(_treasury != address(0), \\\"Treasury address must not be 0x0\\\");\\n\\n self.treasury = _treasury;\\n emit TreasuryUpdated(_treasury);\\n }\\n}\\n\",\"keccak256\":\"0x0fe63c47a08ff0e56bd3bf4d2c9db4b09df717ac77ba5b18ab382a1e0bbb11c7\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Deposit.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\n/// @title Bridge deposit\\n/// @notice The library handles the logic for revealing Bitcoin deposits to\\n/// the Bridge.\\n/// @dev The depositor puts together a P2SH or P2WSH address to deposit the\\n/// funds. This script is unique to each depositor and looks like this:\\n///\\n/// ```\\n/// DROP\\n/// DROP\\n/// DUP HASH160 EQUAL\\n/// IF\\n/// CHECKSIG\\n/// ELSE\\n/// DUP HASH160 EQUALVERIFY\\n/// CHECKLOCKTIMEVERIFY DROP\\n/// CHECKSIG\\n/// ENDIF\\n/// ```\\n///\\n/// Since each depositor has their own Ethereum address and their own\\n/// blinding factor, each depositor\\u2019s script is unique, and the hash\\n/// of each depositor\\u2019s script is unique.\\nlibrary Deposit {\\n using BTCUtils for bytes;\\n using BytesLib for bytes;\\n\\n /// @notice Represents data which must be revealed by the depositor during\\n /// deposit reveal.\\n struct DepositRevealInfo {\\n // Index of the funding output belonging to the funding transaction.\\n uint32 fundingOutputIndex;\\n // The blinding factor as 8 bytes. Byte endianness doesn't matter\\n // as this factor is not interpreted as uint. The blinding factor allows\\n // to distinguish deposits from the same depositor.\\n bytes8 blindingFactor;\\n // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)\\n // of the deposit's wallet hashed in the HASH160 Bitcoin opcode style.\\n bytes20 walletPubKeyHash;\\n // The compressed Bitcoin public key (33 bytes and 02 or 03 prefix)\\n // that can be used to make the deposit refund after the refund\\n // locktime passes. Hashed in the HASH160 Bitcoin opcode style.\\n bytes20 refundPubKeyHash;\\n // The refund locktime (4-byte LE). Interpreted according to locktime\\n // parsing rules described in:\\n // https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number\\n // and used with OP_CHECKLOCKTIMEVERIFY opcode as described in:\\n // https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki\\n bytes4 refundLocktime;\\n // Address of the Bank vault to which the deposit is routed to.\\n // Optional, can be 0x0. The vault must be trusted by the Bridge.\\n address vault;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's calldata argument.\\n }\\n\\n /// @notice Represents tBTC deposit request data.\\n struct DepositRequest {\\n // Ethereum depositor address.\\n address depositor;\\n // Deposit amount in satoshi.\\n uint64 amount;\\n // UNIX timestamp the deposit was revealed at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 revealedAt;\\n // Address of the Bank vault the deposit is routed to.\\n // Optional, can be 0x0.\\n address vault;\\n // Treasury TBTC fee in satoshi at the moment of deposit reveal.\\n uint64 treasuryFee;\\n // UNIX timestamp the deposit was swept at. Note this is not the\\n // time when the deposit was swept on the Bitcoin chain but actually\\n // the time when the sweep proof was delivered to the Ethereum chain.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 sweptAt;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event DepositRevealed(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex,\\n address indexed depositor,\\n uint64 amount,\\n bytes8 blindingFactor,\\n bytes20 indexed walletPubKeyHash,\\n bytes20 refundPubKeyHash,\\n bytes4 refundLocktime,\\n address vault\\n );\\n\\n /// @notice Used by the depositor to reveal information about their P2(W)SH\\n /// Bitcoin deposit to the Bridge on Ethereum chain. The off-chain\\n /// wallet listens for revealed deposit events and may decide to\\n /// include the revealed deposit in the next executed sweep.\\n /// Information about the Bitcoin deposit can be revealed before or\\n /// after the Bitcoin transaction with P2(W)SH deposit is mined on\\n /// the Bitcoin chain. Worth noting, the gas cost of this function\\n /// scales with the number of P2(W)SH transaction inputs and\\n /// outputs. The deposit may be routed to one of the trusted vaults.\\n /// When a deposit is routed to a vault, vault gets notified when\\n /// the deposit gets swept and it may execute the appropriate action.\\n /// @param fundingTx Bitcoin funding transaction data, see `BitcoinTx.Info`.\\n /// @param reveal Deposit reveal data, see `RevealInfo struct.\\n /// @dev Requirements:\\n /// - This function must be called by the same Ethereum address as the\\n /// one used in the P2(W)SH BTC deposit transaction as a depositor,\\n /// - `reveal.walletPubKeyHash` must identify a `Live` wallet,\\n /// - `reveal.vault` must be 0x0 or point to a trusted vault,\\n /// - `reveal.fundingOutputIndex` must point to the actual P2(W)SH\\n /// output of the BTC deposit transaction,\\n /// - `reveal.blindingFactor` must be the blinding factor used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - `reveal.walletPubKeyHash` must be the wallet pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundPubKeyHash` must be the refund pub key hash used in\\n /// the P2(W)SH BTC deposit transaction,\\n /// - `reveal.refundLocktime` must be the refund locktime used in the\\n /// P2(W)SH BTC deposit transaction,\\n /// - BTC deposit for the given `fundingTxHash`, `fundingOutputIndex`\\n /// can be revealed only one time.\\n ///\\n /// If any of these requirements is not met, the wallet _must_ refuse\\n /// to sweep the deposit and the depositor has to wait until the\\n /// deposit script unlocks to receive their BTC back.\\n function revealDeposit(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata fundingTx,\\n DepositRevealInfo calldata reveal\\n ) external {\\n require(\\n self.registeredWallets[reveal.walletPubKeyHash].state ==\\n Wallets.WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n require(\\n reveal.vault == address(0) || self.isVaultTrusted[reveal.vault],\\n \\\"Vault is not trusted\\\"\\n );\\n\\n if (self.depositRevealAheadPeriod > 0) {\\n validateDepositRefundLocktime(self, reveal.refundLocktime);\\n }\\n\\n bytes memory expectedScript = abi.encodePacked(\\n hex\\\"14\\\", // Byte length of depositor Ethereum address.\\n msg.sender,\\n hex\\\"75\\\", // OP_DROP\\n hex\\\"08\\\", // Byte length of blinding factor value.\\n reveal.blindingFactor,\\n hex\\\"75\\\", // OP_DROP\\n hex\\\"76\\\", // OP_DUP\\n hex\\\"a9\\\", // OP_HASH160\\n hex\\\"14\\\", // Byte length of a compressed Bitcoin public key hash.\\n reveal.walletPubKeyHash,\\n hex\\\"87\\\", // OP_EQUAL\\n hex\\\"63\\\", // OP_IF\\n hex\\\"ac\\\", // OP_CHECKSIG\\n hex\\\"67\\\", // OP_ELSE\\n hex\\\"76\\\", // OP_DUP\\n hex\\\"a9\\\", // OP_HASH160\\n hex\\\"14\\\", // Byte length of a compressed Bitcoin public key hash.\\n reveal.refundPubKeyHash,\\n hex\\\"88\\\", // OP_EQUALVERIFY\\n hex\\\"04\\\", // Byte length of refund locktime value.\\n reveal.refundLocktime,\\n hex\\\"b1\\\", // OP_CHECKLOCKTIMEVERIFY\\n hex\\\"75\\\", // OP_DROP\\n hex\\\"ac\\\", // OP_CHECKSIG\\n hex\\\"68\\\" // OP_ENDIF\\n );\\n\\n bytes memory fundingOutput = fundingTx\\n .outputVector\\n .extractOutputAtIndex(reveal.fundingOutputIndex);\\n bytes memory fundingOutputHash = fundingOutput.extractHash();\\n\\n if (fundingOutputHash.length == 20) {\\n // A 20-byte output hash is used by P2SH. That hash is constructed\\n // by applying OP_HASH160 on the locking script. A 20-byte output\\n // hash is used as well by P2PKH and P2WPKH (OP_HASH160 on the\\n // public key). However, since we compare the actual output hash\\n // with an expected locking script hash, this check will succeed only\\n // for P2SH transaction type with expected script hash value. For\\n // P2PKH and P2WPKH, it will fail on the output hash comparison with\\n // the expected locking script hash.\\n require(\\n fundingOutputHash.slice20(0) == expectedScript.hash160View(),\\n \\\"Wrong 20-byte script hash\\\"\\n );\\n } else if (fundingOutputHash.length == 32) {\\n // A 32-byte output hash is used by P2WSH. That hash is constructed\\n // by applying OP_SHA256 on the locking script.\\n require(\\n fundingOutputHash.toBytes32() == sha256(expectedScript),\\n \\\"Wrong 32-byte script hash\\\"\\n );\\n } else {\\n revert(\\\"Wrong script hash length\\\");\\n }\\n\\n // Resulting TX hash is in native Bitcoin little-endian format.\\n bytes32 fundingTxHash = abi\\n .encodePacked(\\n fundingTx.version,\\n fundingTx.inputVector,\\n fundingTx.outputVector,\\n fundingTx.locktime\\n )\\n .hash256View();\\n\\n DepositRequest storage deposit = self.deposits[\\n uint256(\\n keccak256(\\n abi.encodePacked(fundingTxHash, reveal.fundingOutputIndex)\\n )\\n )\\n ];\\n require(deposit.revealedAt == 0, \\\"Deposit already revealed\\\");\\n\\n uint64 fundingOutputAmount = fundingOutput.extractValue();\\n\\n require(\\n fundingOutputAmount >= self.depositDustThreshold,\\n \\\"Deposit amount too small\\\"\\n );\\n\\n deposit.amount = fundingOutputAmount;\\n deposit.depositor = msg.sender;\\n /* solhint-disable-next-line not-rely-on-time */\\n deposit.revealedAt = uint32(block.timestamp);\\n deposit.vault = reveal.vault;\\n deposit.treasuryFee = self.depositTreasuryFeeDivisor > 0\\n ? fundingOutputAmount / self.depositTreasuryFeeDivisor\\n : 0;\\n // slither-disable-next-line reentrancy-events\\n emit DepositRevealed(\\n fundingTxHash,\\n reveal.fundingOutputIndex,\\n msg.sender,\\n fundingOutputAmount,\\n reveal.blindingFactor,\\n reveal.walletPubKeyHash,\\n reveal.refundPubKeyHash,\\n reveal.refundLocktime,\\n reveal.vault\\n );\\n }\\n\\n /// @notice Validates the deposit refund locktime. The validation passes\\n /// successfully only if the deposit reveal is done respectively\\n /// earlier than the moment when the deposit refund locktime is\\n /// reached, i.e. the deposit become refundable. Reverts otherwise.\\n /// @param refundLocktime The deposit refund locktime as 4-byte LE.\\n /// @dev Requirements:\\n /// - `refundLocktime` as integer must be >= 500M\\n /// - `refundLocktime` must denote a timestamp that is at least\\n /// `depositRevealAheadPeriod` seconds later than the moment\\n /// of `block.timestamp`\\n function validateDepositRefundLocktime(\\n BridgeState.Storage storage self,\\n bytes4 refundLocktime\\n ) internal view {\\n // Convert the refund locktime byte array to a LE integer. This is\\n // the moment in time when the deposit become refundable.\\n uint32 depositRefundableTimestamp = BTCUtils.reverseUint32(\\n uint32(refundLocktime)\\n );\\n // According to https://developer.bitcoin.org/devguide/transactions.html#locktime-and-sequence-number\\n // the locktime is parsed as a block number if less than 500M. We always\\n // want to parse the locktime as an Unix timestamp so we allow only for\\n // values bigger than or equal to 500M.\\n require(\\n depositRefundableTimestamp >= 500 * 1e6,\\n \\\"Refund locktime must be a value >= 500M\\\"\\n );\\n // The deposit must be revealed before it becomes refundable.\\n // This is because the sweeping wallet needs to have some time to\\n // sweep the deposit and avoid a potential competition with the\\n // depositor making the deposit refund.\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp + self.depositRevealAheadPeriod <=\\n depositRefundableTimestamp,\\n \\\"Deposit refund locktime is too close\\\"\\n );\\n }\\n}\\n\",\"keccak256\":\"0xc00d41ea9e98a6fa3d8d8701ad8554c459e0268c6bf413f0447bc1c68c6ab7e8\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/DepositSweep.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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 temporary information needed during the processing\\n /// of the deposit sweep Bitcoin transaction inputs. This structure\\n /// is an internal one and should not be exported outside of the\\n /// deposit sweep transaction processing code.\\n /// @dev Allows to mitigate \\\"stack too deep\\\" errors on EVM.\\n struct DepositSweepTxInputsProcessingInfo {\\n // Input vector of the deposit sweep Bitcoin transaction. It is\\n // assumed the vector's structure is valid so it must be validated\\n // using e.g. `BTCUtils.validateVin` function before being used\\n // during the processing. The validation is usually done as part\\n // of the `BitcoinTx.validateProof` call that checks the SPV proof.\\n bytes sweepTxInputVector;\\n // Data of the wallet's main UTXO. If no main UTXO exists for the given\\n // sweeping wallet, this parameter's fields should be zeroed to bypass\\n // the main UTXO validation\\n BitcoinTx.UTXO mainUtxo;\\n // Address of the vault where all swept deposits should be routed to.\\n // It is used to validate whether all swept deposits have been revealed\\n // with the same `vault` parameter. It is an optional parameter.\\n // Set to zero address if deposits are not routed to a vault.\\n address vault;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\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 // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\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 /// @param vault Optional address of the vault where all swept deposits\\n /// should be routed to. All deposits swept as part of the transaction\\n /// must have their `vault` parameters set to the same address.\\n /// If this parameter is set to an address of a trusted vault, swept\\n /// deposits are routed to that vault.\\n /// If this parameter is set to the zero address or to an address\\n /// of a non-trusted vault, swept deposits are not routed to a\\n /// vault but depositors' balances are increased in the Bank\\n /// individually.\\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 /// - All revealed deposits that are swept by `sweepTx` must have\\n /// their `vault` parameters set to the same address as the address\\n /// passed in the `vault` function parameter,\\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 address vault\\n ) external {\\n // Wallet state validation is performed in the\\n // `resolveDepositSweepingWallet` function.\\n\\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 DepositSweepTxInputsProcessingInfo(\\n sweepTx.inputVector,\\n resolvedMainUtxo,\\n vault\\n )\\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 if (vault != address(0) && self.isVaultTrusted[vault]) {\\n // If the `vault` address is not zero and belongs to a trusted\\n // vault, route the deposits to that vault.\\n self.bank.increaseBalanceAndCall(\\n vault,\\n inputsInfo.depositors,\\n inputsInfo.depositedAmounts\\n );\\n } else {\\n // If the `vault` address is zero or belongs to a non-trusted\\n // vault, increase balances in the Bank individually for each\\n // depositor.\\n self.bank.increaseBalances(\\n inputsInfo.depositors,\\n inputsInfo.depositedAmounts\\n );\\n }\\n\\n // Pass the treasury fee to the treasury address.\\n if (totalTreasuryFee > 0) {\\n self.bank.increaseBalance(self.treasury, totalTreasuryFee);\\n }\\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 /// @return resultInfo Outcomes of the processing.\\n function processDepositSweepTxInputs(\\n BridgeState.Storage storage self,\\n DepositSweepTxInputsProcessingInfo memory processInfo\\n ) internal returns (DepositSweepTxInputsInfo memory resultInfo) {\\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 = processInfo.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 (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = processInfo\\n .sweepTxInputVector\\n .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 resultInfo.depositors = new address[](\\n !mainUtxoExpected ? inputsCount : inputsCount - 1\\n );\\n resultInfo.depositedAmounts = new uint256[](\\n resultInfo.depositors.length\\n );\\n resultInfo.treasuryFees = new uint256[](resultInfo.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 processInfo.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 require(\\n deposit.vault == processInfo.vault,\\n \\\"Deposit should be routed to another vault\\\"\\n );\\n\\n if (processedDepositsCount == resultInfo.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 resultInfo.depositors[processedDepositsCount] = deposit\\n .depositor;\\n resultInfo.depositedAmounts[processedDepositsCount] = deposit\\n .amount;\\n resultInfo.inputsTotalValue += resultInfo.depositedAmounts[\\n processedDepositsCount\\n ];\\n resultInfo.treasuryFees[processedDepositsCount] = deposit\\n .treasuryFee;\\n\\n processedDepositsCount++;\\n } else if (\\n mainUtxoExpected != mainUtxoFound &&\\n processInfo.mainUtxo.txHash == outpointTxHash &&\\n processInfo.mainUtxo.txOutputIndex == outpointIndex\\n ) {\\n // If we entered here, that means the input was identified as\\n // the expected main UTXO.\\n resultInfo.inputsTotalValue += processInfo\\n .mainUtxo\\n .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 == resultInfo.depositors.length == resultInfo.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 resultInfo;\\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\",\"keccak256\":\"0xa5b6319960c51c1c5b1edf0539075b822e70d43035eb5f1d20325d0b57cbd73c\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/EcdsaLib.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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\",\"keccak256\":\"0x670d0fbeb088f78abfe7ae7f844784d774ca515480383378a602af707cace7ff\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Fraud.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {CheckBitcoinSigs} from \\\"@keep-network/bitcoin-spv-sol/contracts/CheckBitcoinSigs.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./EcdsaLib.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Heartbeat.sol\\\";\\nimport \\\"./MovingFunds.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\n/// @title Bridge fraud\\n/// @notice The library handles the logic for challenging Bridge wallets that\\n/// committed fraud.\\n/// @dev Anyone can submit a fraud challenge indicating that a UTXO being under\\n/// the wallet control was unlocked by the wallet but was not used\\n/// according to the protocol rules. That means the wallet signed\\n/// a transaction input pointing to that UTXO and there is a unique\\n/// sighash and signature pair associated with that input.\\n///\\n/// In order to defeat the challenge, the same wallet public key and\\n/// signature must be provided as were used to calculate the sighash during\\n/// the challenge. The wallet provides the preimage which produces sighash\\n/// used to generate the ECDSA signature that is the subject of the fraud\\n/// claim.\\n///\\n/// The fraud challenge defeat attempt will succeed if the inputs in the\\n/// preimage are considered honestly spent by the wallet. Therefore the\\n/// transaction spending the UTXO must be proven in the Bridge before\\n/// a challenge defeat is called.\\n///\\n/// Another option is when a malicious wallet member used a signed heartbeat\\n/// message periodically produced by the wallet off-chain to challenge the\\n/// wallet for a fraud. Anyone from the wallet can defeat the challenge by\\n/// proving the sighash and signature were produced for a heartbeat message\\n/// following a strict format.\\nlibrary Fraud {\\n using Wallets for BridgeState.Storage;\\n\\n using BytesLib for bytes;\\n using BTCUtils for bytes;\\n using BTCUtils for uint32;\\n using EcdsaLib for bytes;\\n\\n struct FraudChallenge {\\n // The address of the party challenging the wallet.\\n address challenger;\\n // The amount of ETH the challenger deposited.\\n uint256 depositAmount;\\n // The timestamp the challenge was submitted at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 reportedAt;\\n // The flag indicating whether the challenge has been resolved.\\n bool resolved;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event FraudChallengeSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash,\\n uint8 v,\\n bytes32 r,\\n bytes32 s\\n );\\n\\n event FraudChallengeDefeated(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sighash\\n );\\n\\n event FraudChallengeDefeatTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n // Sighash calculated as a Bitcoin's hash256 (double sha2) of:\\n // - a preimage of a transaction spending UTXO according to the protocol\\n // rules OR\\n // - a valid heartbeat message produced by the wallet off-chain.\\n bytes32 sighash\\n );\\n\\n /// @notice Submits a fraud challenge indicating that a UTXO being under\\n /// wallet control was unlocked by the wallet but was not used\\n /// according to the protocol rules. That means the wallet signed\\n /// a transaction input pointing to that UTXO and there is a unique\\n /// sighash and signature pair associated with that input. This\\n /// function uses those parameters to create a fraud accusation that\\n /// proves a given transaction input unlocking the given UTXO was\\n /// actually signed by the wallet. This function cannot determine\\n /// whether the transaction was actually broadcast and the input was\\n /// consumed in a fraudulent way so it just opens a challenge period\\n /// during which the wallet can defeat the challenge by submitting\\n /// proof of a transaction that consumes the given input according\\n /// to protocol rules. To prevent spurious allegations, the caller\\n /// must deposit ETH that is returned back upon justified fraud\\n /// challenge or confiscated otherwise.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @param signature Bitcoin signature in the R/S/V format\\n /// @dev Requirements:\\n /// - Wallet behind `walletPublicKey` must be in Live or MovingFunds\\n /// or Closing state,\\n /// - The challenger must send appropriate amount of ETH used as\\n /// fraud challenge deposit,\\n /// - The signature (represented by r, s and v) must be generated by\\n /// the wallet behind `walletPubKey` during signing of `sighash`\\n /// which was calculated from `preimageSha256`,\\n /// - Wallet can be challenged for the given signature only once.\\n function submitFraudChallenge(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n bytes memory preimageSha256,\\n BitcoinTx.RSVSignature calldata signature\\n ) external {\\n require(\\n msg.value >= self.fraudChallengeDepositAmount,\\n \\\"The amount of ETH deposited is too low\\\"\\n );\\n\\n // To prevent ECDSA signature forgery `sighash` must be calculated\\n // inside the function and not passed as a function parameter.\\n // Signature forgery could result in a wrongful fraud accusation\\n // against a wallet.\\n bytes32 sighash = sha256(preimageSha256);\\n\\n require(\\n CheckBitcoinSigs.checkSig(\\n walletPublicKey,\\n sighash,\\n signature.v,\\n signature.r,\\n signature.s\\n ),\\n \\\"Signature verification failure\\\"\\n );\\n\\n bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(\\n walletPublicKey.slice32(0),\\n walletPublicKey.slice32(32)\\n );\\n bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();\\n\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.Live ||\\n wallet.state == Wallets.WalletState.MovingFunds ||\\n wallet.state == Wallets.WalletState.Closing,\\n \\\"Wallet must be in Live or MovingFunds or Closing state\\\"\\n );\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n require(challenge.reportedAt == 0, \\\"Fraud challenge already exists\\\");\\n\\n challenge.challenger = msg.sender;\\n challenge.depositAmount = msg.value;\\n /* solhint-disable-next-line not-rely-on-time */\\n challenge.reportedAt = uint32(block.timestamp);\\n challenge.resolved = false;\\n // slither-disable-next-line reentrancy-events\\n emit FraudChallengeSubmitted(\\n walletPubKeyHash,\\n sighash,\\n signature.v,\\n signature.r,\\n signature.s\\n );\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet if\\n /// the transaction that spends the UTXO follows the protocol rules.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during input signing.\\n /// The fraud challenge defeat attempt will only succeed if the\\n /// inputs in the preimage are considered honestly spent by the\\n /// wallet. Therefore the transaction spending the UTXO must be\\n /// proven in the Bridge before a challenge defeat is called.\\n /// If successfully defeated, the fraud challenge is marked as\\n /// resolved and the amount of ether deposited by the challenger is\\n /// sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference.\\n /// @param witness Flag indicating whether the preimage was produced for a\\n /// witness input. True for witness, false for non-witness input.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as `hash256(preimage)`\\n /// must identify an open fraud challenge,\\n /// - the preimage must be a valid preimage of a transaction generated\\n /// according to the protocol rules and already proved in the Bridge,\\n /// - before a defeat attempt is made the transaction that spends the\\n /// given UTXO must be proven in the Bridge.\\n function defeatFraudChallenge(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n bytes calldata preimage,\\n bool witness\\n ) external {\\n bytes32 sighash = preimage.hash256();\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n\\n require(challenge.reportedAt > 0, \\\"Fraud challenge does not exist\\\");\\n require(\\n !challenge.resolved,\\n \\\"Fraud challenge has already been resolved\\\"\\n );\\n\\n // Ensure SIGHASH_ALL type was used during signing, which is represented\\n // by type value `1`.\\n require(extractSighashType(preimage) == 1, \\\"Wrong sighash type\\\");\\n\\n uint256 utxoKey = witness\\n ? extractUtxoKeyFromWitnessPreimage(preimage)\\n : extractUtxoKeyFromNonWitnessPreimage(preimage);\\n\\n // Check that the UTXO key identifies a correctly spent UTXO.\\n require(\\n self.deposits[utxoKey].sweptAt > 0 ||\\n self.spentMainUTXOs[utxoKey] ||\\n self.movedFundsSweepRequests[utxoKey].state ==\\n MovingFunds.MovedFundsSweepRequestState.Processed,\\n \\\"Spent UTXO not found among correctly spent UTXOs\\\"\\n );\\n\\n resolveFraudChallenge(self, walletPublicKey, challenge, sighash);\\n }\\n\\n /// @notice Allows to defeat a pending fraud challenge against a wallet by\\n /// proving the sighash and signature were produced for an off-chain\\n /// wallet heartbeat message following a strict format.\\n /// In order to defeat the challenge the same `walletPublicKey` and\\n /// signature (represented by `r`, `s` and `v`) must be provided as\\n /// were used to calculate the sighash during heartbeat message\\n /// signing. The fraud challenge defeat attempt will only succeed if\\n /// the signed message follows a strict format required for\\n /// heartbeat messages. If successfully defeated, the fraud\\n /// challenge is marked as resolved and the amount of ether\\n /// deposited by the challenger is sent to the treasury.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes),\\n /// @param heartbeatMessage Off-chain heartbeat message meeting the heartbeat\\n /// message format requirements which produces sighash used to\\n /// generate the ECDSA signature that is the subject of the fraud\\n /// claim.\\n /// @dev Requirements:\\n /// - `walletPublicKey` and `sighash` calculated as\\n /// `hash256(heartbeatMessage)` must identify an open fraud challenge,\\n /// - `heartbeatMessage` must follow a strict format of heartbeat\\n /// messages.\\n function defeatFraudChallengeWithHeartbeat(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n bytes calldata heartbeatMessage\\n ) external {\\n bytes32 sighash = heartbeatMessage.hash256();\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n\\n require(challenge.reportedAt > 0, \\\"Fraud challenge does not exist\\\");\\n require(\\n !challenge.resolved,\\n \\\"Fraud challenge has already been resolved\\\"\\n );\\n\\n require(\\n Heartbeat.isValidHeartbeatMessage(heartbeatMessage),\\n \\\"Not a valid heartbeat message\\\"\\n );\\n\\n resolveFraudChallenge(self, walletPublicKey, challenge, sighash);\\n }\\n\\n /// @notice Called only for successfully defeated fraud challenges.\\n /// The fraud challenge is marked as resolved and the amount of\\n /// ether deposited by the challenger is sent to the treasury.\\n /// @dev Requirements:\\n /// - Must be called only for successfully defeated fraud challenges.\\n function resolveFraudChallenge(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n FraudChallenge storage challenge,\\n bytes32 sighash\\n ) internal {\\n // Mark the challenge as resolved as it was successfully defeated\\n challenge.resolved = true;\\n\\n // Send the ether deposited by the challenger to the treasury\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,unchecked-lowlevel,arbitrary-send\\n self.treasury.call{gas: 100000, value: challenge.depositAmount}(\\\"\\\");\\n /* solhint-enable avoid-low-level-calls */\\n\\n bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(\\n walletPublicKey.slice32(0),\\n walletPublicKey.slice32(32)\\n );\\n bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();\\n\\n // slither-disable-next-line reentrancy-events\\n emit FraudChallengeDefeated(walletPubKeyHash, sighash);\\n }\\n\\n /// @notice Notifies about defeat timeout for the given fraud challenge.\\n /// Can be called only if there was a fraud challenge identified by\\n /// the provided `walletPublicKey` and `sighash` and it was not\\n /// defeated on time. The amount of time that needs to pass after\\n /// a fraud challenge is reported is indicated by the\\n /// `challengeDefeatTimeout`. After a successful fraud challenge\\n /// defeat timeout notification the fraud challenge is marked as\\n /// resolved, the stake of each operator is slashed, the ether\\n /// deposited is returned to the challenger and the challenger is\\n /// rewarded.\\n /// @param walletPublicKey The public key of the wallet in the uncompressed\\n /// and unprefixed format (64 bytes).\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param preimageSha256 The hash that was generated by applying SHA-256\\n /// one time over the preimage used during input signing. The preimage\\n /// is a serialized subset of the transaction and its structure\\n /// depends on the transaction input (see BIP-143 for reference).\\n /// Notice that applying SHA-256 over the `preimageSha256` results\\n /// in `sighash`. The path from `preimage` to `sighash` looks like\\n /// this:\\n /// preimage -> (SHA-256) -> preimageSha256 -> (SHA-256) -> sighash.\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Closing or\\n /// Terminated state,\\n /// - The `walletPublicKey` and `sighash` calculated from\\n /// `preimageSha256` must identify an open fraud challenge,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time indicated by `challengeDefeatTimeout` must pass\\n /// after the challenge was reported.\\n function notifyFraudChallengeDefeatTimeout(\\n BridgeState.Storage storage self,\\n bytes calldata walletPublicKey,\\n uint32[] calldata walletMembersIDs,\\n bytes memory preimageSha256\\n ) external {\\n // Wallet state is validated in `notifyWalletFraudChallengeDefeatTimeout`.\\n\\n bytes32 sighash = sha256(preimageSha256);\\n\\n uint256 challengeKey = uint256(\\n keccak256(abi.encodePacked(walletPublicKey, sighash))\\n );\\n\\n FraudChallenge storage challenge = self.fraudChallenges[challengeKey];\\n\\n require(challenge.reportedAt > 0, \\\"Fraud challenge does not exist\\\");\\n\\n require(\\n !challenge.resolved,\\n \\\"Fraud challenge has already been resolved\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >=\\n challenge.reportedAt + self.fraudChallengeDefeatTimeout,\\n \\\"Fraud challenge defeat period did not time out yet\\\"\\n );\\n\\n challenge.resolved = true;\\n // Return the ether deposited by the challenger\\n /* solhint-disable avoid-low-level-calls */\\n // slither-disable-next-line low-level-calls,unchecked-lowlevel\\n challenge.challenger.call{gas: 100000, value: challenge.depositAmount}(\\n \\\"\\\"\\n );\\n /* solhint-enable avoid-low-level-calls */\\n\\n bytes memory compressedWalletPublicKey = EcdsaLib.compressPublicKey(\\n walletPublicKey.slice32(0),\\n walletPublicKey.slice32(32)\\n );\\n bytes20 walletPubKeyHash = compressedWalletPublicKey.hash160View();\\n\\n self.notifyWalletFraudChallengeDefeatTimeout(\\n walletPubKeyHash,\\n walletMembersIDs,\\n challenge.challenger\\n );\\n\\n // slither-disable-next-line reentrancy-events\\n emit FraudChallengeDefeatTimedOut(walletPubKeyHash, sighash);\\n }\\n\\n /// @notice Extracts the UTXO keys from the given preimage used during\\n /// signing of a witness input.\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference\\n /// @return utxoKey UTXO key that identifies spent input.\\n function extractUtxoKeyFromWitnessPreimage(bytes calldata preimage)\\n internal\\n pure\\n returns (uint256 utxoKey)\\n {\\n // The expected structure of the preimage created during signing of a\\n // witness input:\\n // - transaction version (4 bytes)\\n // - hash of previous outpoints of all inputs (32 bytes)\\n // - hash of sequences of all inputs (32 bytes)\\n // - outpoint (hash + index) of the input being signed (36 bytes)\\n // - the unlocking script of the input (variable length)\\n // - value of the outpoint (8 bytes)\\n // - sequence of the input being signed (4 bytes)\\n // - hash of all outputs (32 bytes)\\n // - transaction locktime (4 bytes)\\n // - sighash type (4 bytes)\\n\\n // See Bitcoin's BIP-143 for reference:\\n // https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki.\\n\\n // The outpoint (hash and index) is located at the constant offset of\\n // 68 (4 + 32 + 32).\\n bytes32 outpointTxHash = preimage.extractInputTxIdLeAt(68);\\n uint32 outpointIndex = BTCUtils.reverseUint32(\\n uint32(preimage.extractTxIndexLeAt(68))\\n );\\n\\n return\\n uint256(keccak256(abi.encodePacked(outpointTxHash, outpointIndex)));\\n }\\n\\n /// @notice Extracts the UTXO key from the given preimage used during\\n /// signing of a non-witness input.\\n /// @param preimage The preimage which produces sighash used to generate the\\n /// ECDSA signature that is the subject of the fraud claim. It is a\\n /// serialized subset of the transaction. The exact subset used as\\n /// the preimage depends on the transaction input the signature is\\n /// produced for. See BIP-143 for reference.\\n /// @return utxoKey UTXO key that identifies spent input.\\n function extractUtxoKeyFromNonWitnessPreimage(bytes calldata preimage)\\n internal\\n pure\\n returns (uint256 utxoKey)\\n {\\n // The expected structure of the preimage created during signing of a\\n // non-witness input:\\n // - transaction version (4 bytes)\\n // - number of inputs written as compactSize uint (1 byte, 3 bytes,\\n // 5 bytes or 9 bytes)\\n // - for each input\\n // - outpoint (hash and index) (36 bytes)\\n // - unlocking script for the input being signed (variable length)\\n // or `00` for all other inputs (1 byte)\\n // - input sequence (4 bytes)\\n // - number of outputs written as compactSize uint (1 byte, 3 bytes,\\n // 5 bytes or 9 bytes)\\n // - outputs (variable length)\\n // - transaction locktime (4 bytes)\\n // - sighash type (4 bytes)\\n\\n // See example for reference:\\n // https://en.bitcoin.it/wiki/OP_CHECKSIG#Code_samples_and_raw_dumps.\\n\\n // The input data begins at the constant offset of 4 (the first 4 bytes\\n // are for the transaction version).\\n (uint256 inputsCompactSizeUintLength, uint256 inputsCount) = preimage\\n .parseVarIntAt(4);\\n\\n // To determine the first input starting index, we must jump 4 bytes\\n // over the transaction version length and the compactSize uint which\\n // prepends the input vector. One byte must be added because\\n // `BtcUtils.parseVarInt` does not include compactSize uint tag in the\\n // 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 = 4 + 1 + inputsCompactSizeUintLength;\\n\\n for (uint256 i = 0; i < inputsCount; i++) {\\n uint256 inputLength = preimage.determineInputLengthAt(\\n inputStartingIndex\\n );\\n\\n (, uint256 scriptSigLength) = preimage.extractScriptSigLenAt(\\n inputStartingIndex\\n );\\n\\n if (scriptSigLength > 0) {\\n // The input this preimage was generated for was found.\\n // All the other inputs in the preimage are marked with a null\\n // scriptSig (\\\"00\\\") which has length of 1.\\n bytes32 outpointTxHash = preimage.extractInputTxIdLeAt(\\n inputStartingIndex\\n );\\n uint32 outpointIndex = BTCUtils.reverseUint32(\\n uint32(preimage.extractTxIndexLeAt(inputStartingIndex))\\n );\\n\\n utxoKey = uint256(\\n keccak256(abi.encodePacked(outpointTxHash, outpointIndex))\\n );\\n\\n break;\\n }\\n\\n inputStartingIndex += inputLength;\\n }\\n\\n return utxoKey;\\n }\\n\\n /// @notice Extracts the sighash type from the given preimage.\\n /// @param preimage Serialized subset of the transaction. See BIP-143 for\\n /// reference.\\n /// @dev Sighash type is stored as the last 4 bytes in the preimage (little\\n /// endian).\\n /// @return sighashType Sighash type as a 32-bit integer.\\n function extractSighashType(bytes calldata preimage)\\n internal\\n pure\\n returns (uint32 sighashType)\\n {\\n bytes4 sighashTypeBytes = preimage.slice4(preimage.length - 4);\\n uint32 sighashTypeLE = uint32(sighashTypeBytes);\\n return sighashTypeLE.reverseUint32();\\n }\\n}\\n\",\"keccak256\":\"0x5b976a45cbee8e5d8e65fc748820e36c692d8fb26a8532319a87e6a614733c58\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Heartbeat.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\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).\\n///\\n/// The message being signed by the wallet when executing the heartbeat\\n/// protocol should be Bitcoin's hash256 (double SHA-256) of the heartbeat\\n/// message:\\n/// heartbeat_sighash = hash256(heartbeat_message)\\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\",\"keccak256\":\"0x2f3ad70beadc4dfb6414064fd7b3621b1edcd2713f186853b4a5fd36fb4502ba\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/IRelay.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\n/// @title Interface for the Bitcoin relay\\n/// @notice Contains only the methods needed by tBTC v2. The Bitcoin relay\\n/// provides the difficulty of the previous and current epoch. One\\n/// difficulty epoch spans 2016 blocks.\\ninterface IRelay {\\n /// @notice Returns the difficulty of the current epoch.\\n function getCurrentEpochDifficulty() external view returns (uint256);\\n\\n /// @notice Returns the difficulty of the previous epoch.\\n function getPrevEpochDifficulty() external view returns (uint256);\\n}\\n\",\"keccak256\":\"0xf70c723fc0a1824d92061b5dc76c65c38c22eff6b18ef6a2057f511183ce3c5b\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/MovingFunds.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Redemption.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\n/// @title Moving Bridge wallet funds\\n/// @notice The library handles the logic for moving Bitcoin between Bridge\\n/// wallets.\\n/// @dev A wallet that failed a heartbeat, did not process requested redemption\\n/// on time, or qualifies to be closed, begins the procedure of moving\\n/// funds to other wallets in the Bridge. The wallet needs to commit to\\n/// which other Live wallets it is moving the funds to and then, provide an\\n/// SPV proof of moving funds to the previously committed wallets.\\n/// Once the proof is submitted, all target wallets are supposed to\\n/// sweep the received UTXOs with their own main UTXOs in order to\\n/// update their BTC balances.\\nlibrary MovingFunds {\\n using BridgeState for BridgeState.Storage;\\n using Wallets for BridgeState.Storage;\\n using BitcoinTx for BridgeState.Storage;\\n\\n using BTCUtils for bytes;\\n using BytesLib for bytes;\\n\\n /// @notice Represents temporary information needed during the processing\\n /// of the moving funds Bitcoin transaction outputs. This structure\\n /// is an internal one and should not be exported outside of the\\n /// moving funds transaction processing code.\\n /// @dev Allows to mitigate \\\"stack too deep\\\" errors on EVM.\\n struct MovingFundsTxOutputsProcessingInfo {\\n // 32-byte hash of the moving funds Bitcoin transaction.\\n bytes32 movingFundsTxHash;\\n // Output vector of the moving funds Bitcoin transaction. It is\\n // assumed the vector's structure is valid so it must be validated\\n // using e.g. `BTCUtils.validateVout` function before being used\\n // during the processing. The validation is usually done as part\\n // of the `BitcoinTx.validateProof` call that checks the SPV proof.\\n bytes movingFundsTxOutputVector;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\n\\n /// @notice Represents moved funds sweep request state.\\n enum MovedFundsSweepRequestState {\\n /// @dev The request is unknown to the Bridge.\\n Unknown,\\n /// @dev Request is pending and can become either processed or timed out.\\n Pending,\\n /// @dev Request was processed by the target wallet.\\n Processed,\\n /// @dev Request was not processed in the given time window and\\n /// the timeout was reported.\\n TimedOut\\n }\\n\\n /// @notice Represents a moved funds sweep request. The request is\\n /// registered in `submitMovingFundsProof` where we know funds\\n /// have been moved to the target wallet and the only step left is\\n /// to have the target wallet sweep them.\\n struct MovedFundsSweepRequest {\\n // 20-byte public key hash of the wallet supposed to sweep the UTXO\\n // representing the received funds with their own main UTXO\\n bytes20 walletPubKeyHash;\\n // Value of the received funds.\\n uint64 value;\\n // UNIX timestamp the request was created at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 createdAt;\\n // The current state of the request.\\n MovedFundsSweepRequestState state;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event MovingFundsCommitmentSubmitted(\\n bytes20 indexed walletPubKeyHash,\\n bytes20[] targetWallets,\\n address submitter\\n );\\n\\n event MovingFundsTimeoutReset(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash\\n );\\n\\n event MovingFundsTimedOut(bytes20 indexed walletPubKeyHash);\\n\\n event MovingFundsBelowDustReported(bytes20 indexed walletPubKeyHash);\\n\\n event MovedFundsSwept(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 sweepTxHash\\n );\\n\\n event MovedFundsSweepTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex\\n );\\n\\n /// @notice Submits the moving funds target wallets commitment.\\n /// Once all requirements are met, that function registers the\\n /// target wallets commitment and opens the way for moving funds\\n /// proof submission.\\n /// @param walletPubKeyHash 20-byte public key hash of the source wallet.\\n /// @param walletMainUtxo Data of the source wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @param walletMembersIDs Identifiers of the source wallet signing group\\n /// members.\\n /// @param walletMemberIndex Position of the caller in the source wallet\\n /// signing group members list.\\n /// @param targetWallets List of 20-byte public key hashes of the target\\n /// wallets that the source wallet commits to move the funds to.\\n /// @dev Requirements:\\n /// - The source wallet must be in the MovingFunds state,\\n /// - The source wallet must not have pending redemption requests,\\n /// - The source wallet must not have pending moved funds sweep requests,\\n /// - The source wallet must not have submitted its commitment already,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given source wallet in the ECDSA registry. Those IDs are\\n /// not directly stored in the contract for gas efficiency purposes\\n /// but they can be read from appropriate `DkgResultSubmitted`\\n /// and `DkgResultApproved` events,\\n /// - The `walletMemberIndex` must be in range [1, walletMembersIDs.length],\\n /// - The caller must be the member of the source wallet signing group\\n /// at the position indicated by `walletMemberIndex` parameter,\\n /// - The `walletMainUtxo` components must point to the recent main\\n /// UTXO of the source wallet, as currently known on the Ethereum\\n /// chain,\\n /// - Source wallet BTC balance must be greater than zero,\\n /// - At least one Live wallet must exist in the system,\\n /// - Submitted target wallets count must match the expected count\\n /// `N = min(liveWalletsCount, ceil(walletBtcBalance / walletMaxBtcTransfer))`\\n /// where `N > 0`,\\n /// - Each target wallet must be not equal to the source wallet,\\n /// - Each target wallet must follow the expected order i.e. all\\n /// target wallets 20-byte public key hashes represented as numbers\\n /// must form a strictly increasing sequence without duplicates,\\n /// - Each target wallet must be in Live state.\\n function submitMovingFundsCommitment(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo,\\n uint32[] calldata walletMembersIDs,\\n uint256 walletMemberIndex,\\n bytes20[] calldata targetWallets\\n ) external {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.MovingFunds,\\n \\\"Source wallet must be in MovingFunds state\\\"\\n );\\n\\n require(\\n wallet.pendingRedemptionsValue == 0,\\n \\\"Source wallet must handle all pending redemptions first\\\"\\n );\\n\\n require(\\n wallet.pendingMovedFundsSweepRequestsCount == 0,\\n \\\"Source wallet must handle all pending moved funds sweep requests first\\\"\\n );\\n\\n require(\\n wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),\\n \\\"Target wallets commitment already submitted\\\"\\n );\\n\\n require(\\n self.ecdsaWalletRegistry.isWalletMember(\\n wallet.ecdsaWalletID,\\n walletMembersIDs,\\n msg.sender,\\n walletMemberIndex\\n ),\\n \\\"Caller is not a member of the source wallet\\\"\\n );\\n\\n uint64 walletBtcBalance = self.getWalletBtcBalance(\\n walletPubKeyHash,\\n walletMainUtxo\\n );\\n\\n require(walletBtcBalance > 0, \\\"Wallet BTC balance is zero\\\");\\n\\n uint256 expectedTargetWalletsCount = Math.min(\\n self.liveWalletsCount,\\n Math.ceilDiv(walletBtcBalance, self.walletMaxBtcTransfer)\\n );\\n\\n // This requirement fails only when `liveWalletsCount` is zero. In\\n // that case, the system cannot accept the commitment and must provide\\n // new wallets first. However, the wallet supposed to submit the\\n // commitment can keep resetting the moving funds timeout until then.\\n require(expectedTargetWalletsCount > 0, \\\"No target wallets available\\\");\\n\\n require(\\n targetWallets.length == expectedTargetWalletsCount,\\n \\\"Submitted target wallets count is other than expected\\\"\\n );\\n\\n uint160 lastProcessedTargetWallet = 0;\\n\\n for (uint256 i = 0; i < targetWallets.length; i++) {\\n bytes20 targetWallet = targetWallets[i];\\n\\n require(\\n targetWallet != walletPubKeyHash,\\n \\\"Submitted target wallet cannot be equal to the source wallet\\\"\\n );\\n\\n require(\\n uint160(targetWallet) > lastProcessedTargetWallet,\\n \\\"Submitted target wallet breaks the expected order\\\"\\n );\\n\\n require(\\n self.registeredWallets[targetWallet].state ==\\n Wallets.WalletState.Live,\\n \\\"Submitted target wallet must be in Live state\\\"\\n );\\n\\n lastProcessedTargetWallet = uint160(targetWallet);\\n }\\n\\n wallet.movingFundsTargetWalletsCommitmentHash = keccak256(\\n abi.encodePacked(targetWallets)\\n );\\n\\n emit MovingFundsCommitmentSubmitted(\\n walletPubKeyHash,\\n targetWallets,\\n msg.sender\\n );\\n }\\n\\n /// @notice Resets the moving funds timeout for the given wallet if the\\n /// target wallet commitment cannot be submitted due to a lack\\n /// of live wallets in the system.\\n /// @param walletPubKeyHash 20-byte public key hash of the moving funds wallet\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The target wallets commitment must not be already submitted for\\n /// the given moving funds wallet,\\n /// - Live wallets count must be zero,\\n /// - The moving funds timeout reset delay must be elapsed.\\n function resetMovingFundsTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) external {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n // If the moving funds wallet already submitted their target wallets\\n // commitment, there is no point to reset the timeout since the\\n // wallet can make the BTC transaction and submit the proof.\\n require(\\n wallet.movingFundsTargetWalletsCommitmentHash == bytes32(0),\\n \\\"Target wallets commitment already submitted\\\"\\n );\\n\\n require(self.liveWalletsCount == 0, \\\"Live wallets count must be zero\\\");\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >\\n wallet.movingFundsRequestedAt +\\n self.movingFundsTimeoutResetDelay,\\n \\\"Moving funds timeout cannot be reset yet\\\"\\n );\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.movingFundsRequestedAt = uint32(block.timestamp);\\n\\n emit MovingFundsTimeoutReset(walletPubKeyHash);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moving funds transaction\\n /// and to make the necessary state changes. Moving funds is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the moving funds transaction structure\\n /// by checking if it actually spends the main UTXO of the declared\\n /// wallet and locks the value on the pre-committed target wallets\\n /// using a reasonable transaction fee. If all preconditions are\\n /// met, this functions closes the source wallet.\\n ///\\n /// It is possible to prove the given moving funds transaction only\\n /// one time.\\n /// @param movingFundsTx Bitcoin moving funds transaction data.\\n /// @param movingFundsProof Bitcoin moving funds proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet\\n /// which performed the moving funds transaction.\\n /// @dev Requirements:\\n /// - `movingFundsTx` 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 `movingFundsTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs corresponding to the\\n /// pre-committed target wallets. Outputs must be ordered in the\\n /// same way as their corresponding target wallets are ordered\\n /// within the target wallets commitment,\\n /// - `movingFundsProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input,\\n /// - The wallet that `walletPubKeyHash` points to must be in the\\n /// MovingFunds state,\\n /// - The target wallets commitment must be submitted by the wallet\\n /// that `walletPubKeyHash` points to,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movingFundsTxMaxTotalFee` governable parameter.\\n function submitMovingFundsProof(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata movingFundsTx,\\n BitcoinTx.Proof calldata movingFundsProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external {\\n // Wallet state is validated in `notifyWalletFundsMoved`.\\n\\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 movingFundsTxHash = self.validateProof(\\n movingFundsTx,\\n movingFundsProof\\n );\\n\\n // Assert that main UTXO for passed wallet exists in storage.\\n bytes32 mainUtxoHash = self\\n .registeredWallets[walletPubKeyHash]\\n .mainUtxoHash;\\n require(mainUtxoHash != bytes32(0), \\\"No main UTXO for given wallet\\\");\\n\\n // Assert that passed main UTXO parameter is the same as in storage and\\n // can be used for further processing.\\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\\n // Process the moving funds transaction input. Specifically, check if\\n // it refers to the expected wallet's main UTXO.\\n OutboundTx.processWalletOutboundTxInput(\\n self,\\n movingFundsTx.inputVector,\\n mainUtxo\\n );\\n\\n (\\n bytes32 targetWalletsHash,\\n uint256 outputsTotalValue\\n ) = processMovingFundsTxOutputs(\\n self,\\n MovingFundsTxOutputsProcessingInfo(\\n movingFundsTxHash,\\n movingFundsTx.outputVector\\n )\\n );\\n\\n require(\\n mainUtxo.txOutputValue - outputsTotalValue <=\\n self.movingFundsTxMaxTotalFee,\\n \\\"Transaction fee is too high\\\"\\n );\\n\\n self.notifyWalletFundsMoved(walletPubKeyHash, targetWalletsHash);\\n // slither-disable-next-line reentrancy-events\\n emit MovingFundsCompleted(walletPubKeyHash, movingFundsTxHash);\\n }\\n\\n /// @notice Processes the moving funds Bitcoin transaction output vector\\n /// and extracts information required for further processing.\\n /// @param processInfo Processing info containing the moving funds tx\\n /// hash and output vector.\\n /// @return targetWalletsHash keccak256 hash over the list of actual\\n /// target wallets used in the transaction.\\n /// @return outputsTotalValue Sum of all outputs values.\\n /// @dev Requirements:\\n /// - The `movingFundsTxOutputVector` must be parseable, i.e. must\\n /// be validated by the caller as stated in their parameter doc,\\n /// - Each output must refer to a 20-byte public key hash,\\n /// - The total outputs value must be evenly divided over all outputs.\\n function processMovingFundsTxOutputs(\\n BridgeState.Storage storage self,\\n MovingFundsTxOutputsProcessingInfo memory processInfo\\n ) internal returns (bytes32 targetWalletsHash, uint256 outputsTotalValue) {\\n // Determining the total number of Bitcoin transaction outputs in\\n // the same way as for number of inputs. See `BitcoinTx.outputVector`\\n // docs for more details.\\n (\\n uint256 outputsCompactSizeUintLength,\\n uint256 outputsCount\\n ) = processInfo.movingFundsTxOutputVector.parseVarInt();\\n\\n // To determine the first output starting index, we must jump over\\n // the compactSize uint which prepends the output 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 outputStartingIndex = 1 + outputsCompactSizeUintLength;\\n\\n bytes20[] memory targetWallets = new bytes20[](outputsCount);\\n uint64[] memory outputsValues = new uint64[](outputsCount);\\n\\n // Outputs processing loop. Note that the `outputIndex` must be\\n // `uint32` to build proper `movedFundsSweepRequests` keys.\\n for (\\n uint32 outputIndex = 0;\\n outputIndex < outputsCount;\\n outputIndex++\\n ) {\\n uint256 outputLength = processInfo\\n .movingFundsTxOutputVector\\n .determineOutputLengthAt(outputStartingIndex);\\n\\n bytes memory output = processInfo.movingFundsTxOutputVector.slice(\\n outputStartingIndex,\\n outputLength\\n );\\n\\n bytes20 targetWalletPubKeyHash = self.extractPubKeyHash(output);\\n\\n // Add the wallet public key hash to the list that will be used\\n // to build the result list hash. There is no need to check if\\n // given output is a change here because the actual target wallet\\n // list must be exactly the same as the pre-committed target wallet\\n // list which is guaranteed to be valid.\\n targetWallets[outputIndex] = targetWalletPubKeyHash;\\n\\n // Extract the value from given output.\\n outputsValues[outputIndex] = output.extractValue();\\n outputsTotalValue += outputsValues[outputIndex];\\n\\n // Register a moved funds sweep request that must be handled\\n // by the target wallet. The target wallet must sweep the\\n // received funds with their own main UTXO in order to update\\n // their BTC balance. Worth noting there is no need to check\\n // if the sweep request already exists in the system because\\n // the moving funds wallet is moved to the Closing state after\\n // submitting the moving funds proof so there is no possibility\\n // to submit the proof again and register the sweep request twice.\\n self.movedFundsSweepRequests[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n processInfo.movingFundsTxHash,\\n outputIndex\\n )\\n )\\n )\\n ] = MovedFundsSweepRequest(\\n targetWalletPubKeyHash,\\n outputsValues[outputIndex],\\n /* solhint-disable-next-line not-rely-on-time */\\n uint32(block.timestamp),\\n MovedFundsSweepRequestState.Pending\\n );\\n // We added a new moved funds sweep request for the target wallet\\n // so we must increment their request counter.\\n self\\n .registeredWallets[targetWalletPubKeyHash]\\n .pendingMovedFundsSweepRequestsCount++;\\n\\n // Make the `outputStartingIndex` pointing to the next output by\\n // increasing it by current output's length.\\n outputStartingIndex += outputLength;\\n }\\n\\n // Compute the indivisible remainder that remains after dividing the\\n // outputs total value over all outputs evenly.\\n uint256 outputsTotalValueRemainder = outputsTotalValue % outputsCount;\\n // Compute the minimum allowed output value by dividing the outputs\\n // total value (reduced by the remainder) by the number of outputs.\\n uint256 minOutputValue = (outputsTotalValue -\\n outputsTotalValueRemainder) / outputsCount;\\n // Maximum possible value is the minimum value with the remainder included.\\n uint256 maxOutputValue = minOutputValue + outputsTotalValueRemainder;\\n\\n for (uint256 i = 0; i < outputsCount; i++) {\\n require(\\n minOutputValue <= outputsValues[i] &&\\n outputsValues[i] <= maxOutputValue,\\n \\\"Transaction amount is not distributed evenly\\\"\\n );\\n }\\n\\n targetWalletsHash = keccak256(abi.encodePacked(targetWallets));\\n\\n return (targetWalletsHash, outputsTotalValue);\\n }\\n\\n /// @notice Notifies about a timed out moving funds process. Terminates\\n /// the wallet and slashes signing group members as a result.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The moving funds timeout must be actually exceeded,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovingFundsTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n // Wallet state is validated in `notifyWalletMovingFundsTimeout`.\\n\\n uint32 movingFundsRequestedAt = self\\n .registeredWallets[walletPubKeyHash]\\n .movingFundsRequestedAt;\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp > movingFundsRequestedAt + self.movingFundsTimeout,\\n \\\"Moving funds has not timed out yet\\\"\\n );\\n\\n self.notifyWalletMovingFundsTimeout(walletPubKeyHash, walletMembersIDs);\\n\\n // slither-disable-next-line reentrancy-events\\n emit MovingFundsTimedOut(walletPubKeyHash);\\n }\\n\\n /// @notice Notifies about a moving funds wallet whose BTC balance is\\n /// below the moving funds dust threshold. Ends the moving funds\\n /// process and begins wallet closing immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known\\n /// on the Ethereum chain.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state,\\n /// - The `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain.\\n /// If the wallet has no main UTXO, this parameter can be empty as it\\n /// is ignored,\\n /// - The wallet BTC balance must be below the moving funds threshold.\\n function notifyMovingFundsBelowDust(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external {\\n // Wallet state is validated in `notifyWalletMovingFundsBelowDust`.\\n\\n uint64 walletBtcBalance = self.getWalletBtcBalance(\\n walletPubKeyHash,\\n mainUtxo\\n );\\n\\n require(\\n walletBtcBalance < self.movingFundsDustThreshold,\\n \\\"Wallet BTC balance must be below the moving funds dust threshold\\\"\\n );\\n\\n self.notifyWalletMovingFundsBelowDust(walletPubKeyHash);\\n\\n // slither-disable-next-line reentrancy-events\\n emit MovingFundsBelowDustReported(walletPubKeyHash);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC moved funds sweep\\n /// transaction and to make the necessary state changes. Moved\\n /// funds sweep is only accepted if it satisfies SPV proof.\\n ///\\n /// The function validates the sweep transaction structure by\\n /// checking if it actually spends the moved funds UTXO and the\\n /// sweeping wallet's main UTXO (optionally), and if it locks the\\n /// value on the sweeping wallet's 20-byte public key hash using a\\n /// reasonable transaction fee. If all preconditions are\\n /// met, this function updates the sweeping wallet main UTXO, thus\\n /// their BTC balance.\\n ///\\n /// It is possible to prove the given sweep transaction only\\n /// one time.\\n /// @param sweepTx Bitcoin sweep funds transaction data.\\n /// @param sweepProof Bitcoin sweep funds proof data.\\n /// @param mainUtxo Data of the sweeping wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\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\\n /// the first input pointing to a wallet's sweep Pending request and,\\n /// optionally, the second input pointing to the wallet's main UTXO,\\n /// if the sweeping wallet has a main UTXO set. There should be only\\n /// one output locking funds on the sweeping wallet 20-byte public\\n /// key hash,\\n /// - `sweepProof` components must match the expected structure.\\n /// See `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 sweeping wallet, as currently known on the Ethereum chain.\\n /// If there is no main UTXO, this parameter is ignored,\\n /// - The sweeping wallet must be in the Live or MovingFunds state,\\n /// - The total Bitcoin transaction fee must be lesser or equal\\n /// to `movedFundsSweepTxMaxTotalFee` governable parameter.\\n function submitMovedFundsSweepProof(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata sweepTx,\\n BitcoinTx.Proof calldata sweepProof,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) external {\\n // Wallet state validation is performed in the\\n // `resolveMovedFundsSweepingWallet` function.\\n\\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 (\\n bytes20 walletPubKeyHash,\\n uint64 sweepTxOutputValue\\n ) = processMovedFundsSweepTxOutput(self, sweepTx.outputVector);\\n\\n (\\n Wallets.Wallet storage wallet,\\n BitcoinTx.UTXO memory resolvedMainUtxo\\n ) = resolveMovedFundsSweepingWallet(self, walletPubKeyHash, mainUtxo);\\n\\n uint256 sweepTxInputsTotalValue = processMovedFundsSweepTxInputs(\\n self,\\n sweepTx.inputVector,\\n resolvedMainUtxo,\\n walletPubKeyHash\\n );\\n\\n require(\\n sweepTxInputsTotalValue - sweepTxOutputValue <=\\n self.movedFundsSweepTxMaxTotalFee,\\n \\\"Transaction fee is too high\\\"\\n );\\n\\n // Use the sweep transaction output as the new sweeping wallet's main UTXO.\\n // Transaction output index is always 0 as sweep transaction always\\n // 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 MovedFundsSwept(walletPubKeyHash, sweepTxHash);\\n }\\n\\n /// @notice Processes the Bitcoin moved funds sweep transaction output vector\\n /// by 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 moved funds sweep transaction output\\n /// 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 moved funds sweep transaction output value.\\n /// @dev Requirements:\\n /// - Output vector must contain only one output,\\n /// - The single output must be of P2PKH or P2WPKH type and lock the\\n /// funds on a 20-byte public key hash.\\n function processMovedFundsSweepTxOutput(\\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 performed as\\n // part of the `BitcoinTx.validateProof` call.\\n // See `BitcoinTx.outputVector` docs for more details.\\n (, uint256 outputsCount) = sweepTxOutputVector.parseVarInt();\\n require(\\n outputsCount == 1,\\n \\\"Moved funds 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 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 resolveMovedFundsSweepingWallet(\\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 moved funds sweep transaction input vector.\\n /// It extracts the first input and tries to match it with one of\\n /// the moved funds sweep requests targeting the sweeping wallet.\\n /// If the sweep request is an existing Pending request, this\\n /// function marks it as Processed. If the sweeping wallet has a\\n /// main UTXO, this function extracts the second input, makes sure\\n /// it refers to the wallet main UTXO, and marks that main UTXO as\\n /// correctly spent.\\n /// @param sweepTxInputVector Bitcoin moved funds 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 sweeping 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 /// @param walletPubKeyHash 20-byte public key hash of the sweeping wallet.\\n /// @return inputsTotalValue Total inputs value sum.\\n /// @dev Requirements:\\n /// - The input vector must consist of one mandatory and one optional\\n /// input,\\n /// - The mandatory input must be the first input in the vector,\\n /// - The mandatory input must point to a Pending moved funds sweep\\n /// request that is targeted to the sweeping wallet,\\n /// - The optional output must be the second input in the vector,\\n /// - The optional input is required if the sweeping wallet has a\\n /// main UTXO (i.e. the `mainUtxo` is not zeroed). In that case,\\n /// that input must point the the sweeping wallet main UTXO.\\n function processMovedFundsSweepTxInputs(\\n BridgeState.Storage storage self,\\n bytes memory sweepTxInputVector,\\n BitcoinTx.UTXO memory mainUtxo,\\n bytes20 walletPubKeyHash\\n ) internal returns (uint256 inputsTotalValue) {\\n // To determine the total number of Bitcoin transaction inputs,\\n // we need to parse the compactSize uint (VarInt) the input vector is\\n // prepended by. That compactSize uint encodes the number of vector\\n // elements using the 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 `validateVin` validation performed as\\n // part of the `BitcoinTx.validateProof` call.\\n // See `BitcoinTx.inputVector` docs for 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 // We always expect the first input to be the swept UTXO. Additionally,\\n // if the sweeping wallet has a main UTXO, that main UTXO should be\\n // pointed by the second input.\\n require(\\n inputsCount == (mainUtxo.txHash != bytes32(0) ? 2 : 1),\\n \\\"Moved funds sweep transaction must have a proper inputs count\\\"\\n );\\n\\n // Parse the first input and extract its outpoint tx hash and index.\\n (\\n bytes32 firstInputOutpointTxHash,\\n uint32 firstInputOutpointIndex,\\n uint256 firstInputLength\\n ) = parseMovedFundsSweepTxInputAt(\\n sweepTxInputVector,\\n inputStartingIndex\\n );\\n\\n // Build the request key and fetch the corresponding moved funds sweep\\n // request from contract storage.\\n MovedFundsSweepRequest storage sweepRequest = self\\n .movedFundsSweepRequests[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n firstInputOutpointTxHash,\\n firstInputOutpointIndex\\n )\\n )\\n )\\n ];\\n\\n require(\\n sweepRequest.state == MovedFundsSweepRequestState.Pending,\\n \\\"Sweep request must be in Pending state\\\"\\n );\\n // We must check if the wallet extracted from the moved funds sweep\\n // transaction output is truly the owner of the sweep request connected\\n // with the swept UTXO. This is needed to prevent a case when a wallet\\n // handles its own sweep request but locks the funds on another\\n // wallet public key hash.\\n require(\\n sweepRequest.walletPubKeyHash == walletPubKeyHash,\\n \\\"Sweep request belongs to another wallet\\\"\\n );\\n // If the validation passed, the sweep request must be marked as\\n // processed and its value should be counted into the total inputs\\n // value sum.\\n sweepRequest.state = MovedFundsSweepRequestState.Processed;\\n inputsTotalValue += sweepRequest.value;\\n\\n self\\n .registeredWallets[walletPubKeyHash]\\n .pendingMovedFundsSweepRequestsCount--;\\n\\n // If the main UTXO for the sweeping wallet exists, it must be processed.\\n if (mainUtxo.txHash != bytes32(0)) {\\n // The second input is supposed to point to that sweeping wallet\\n // main UTXO. We need to parse that input.\\n (\\n bytes32 secondInputOutpointTxHash,\\n uint32 secondInputOutpointIndex,\\n\\n ) = parseMovedFundsSweepTxInputAt(\\n sweepTxInputVector,\\n inputStartingIndex + firstInputLength\\n );\\n // Make sure the second input refers to the sweeping wallet main UTXO.\\n require(\\n mainUtxo.txHash == secondInputOutpointTxHash &&\\n mainUtxo.txOutputIndex == secondInputOutpointIndex,\\n \\\"Second input must point to the wallet's main UTXO\\\"\\n );\\n\\n // If the validation passed, count the main UTXO value into the\\n // total inputs value sum.\\n inputsTotalValue += mainUtxo.txOutputValue;\\n\\n // Main UTXO used as an input, mark it as spent. This is needed\\n // to defend against fraud challenges referring to this main UTXO.\\n self.spentMainUTXOs[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n secondInputOutpointTxHash,\\n secondInputOutpointIndex\\n )\\n )\\n )\\n ] = true;\\n }\\n\\n return inputsTotalValue;\\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 parseMovedFundsSweepTxInputAt(\\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 Notifies about a timed out moved funds sweep process. If the\\n /// wallet is not terminated yet, that function terminates\\n /// the wallet and slashes signing group members as a result.\\n /// Marks the given sweep request as TimedOut.\\n /// @param movingFundsTxHash 32-byte hash of the moving funds transaction\\n /// that caused the sweep request to be created.\\n /// @param movingFundsTxOutputIndex Index of the moving funds transaction\\n /// output that is subject of the sweep request.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The moved funds sweep request must be in the Pending state,\\n /// - The moved funds sweep timeout must be actually exceeded,\\n /// - The wallet must be either in the Live or MovingFunds or\\n /// Terminated state,,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract.\\n function notifyMovedFundsSweepTimeout(\\n BridgeState.Storage storage self,\\n bytes32 movingFundsTxHash,\\n uint32 movingFundsTxOutputIndex,\\n uint32[] calldata walletMembersIDs\\n ) external {\\n // Wallet state is validated in `notifyWalletMovedFundsSweepTimeout`.\\n\\n MovedFundsSweepRequest storage sweepRequest = self\\n .movedFundsSweepRequests[\\n uint256(\\n keccak256(\\n abi.encodePacked(\\n movingFundsTxHash,\\n movingFundsTxOutputIndex\\n )\\n )\\n )\\n ];\\n\\n require(\\n sweepRequest.state == MovedFundsSweepRequestState.Pending,\\n \\\"Sweep request must be in Pending state\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >\\n sweepRequest.createdAt + self.movedFundsSweepTimeout,\\n \\\"Sweep request has not timed out yet\\\"\\n );\\n\\n bytes20 walletPubKeyHash = sweepRequest.walletPubKeyHash;\\n\\n self.notifyWalletMovedFundsSweepTimeout(\\n walletPubKeyHash,\\n walletMembersIDs\\n );\\n\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n sweepRequest.state = MovedFundsSweepRequestState.TimedOut;\\n wallet.pendingMovedFundsSweepRequestsCount--;\\n\\n // slither-disable-next-line reentrancy-events\\n emit MovedFundsSweepTimedOut(\\n walletPubKeyHash,\\n movingFundsTxHash,\\n movingFundsTxOutputIndex\\n );\\n }\\n}\\n\",\"keccak256\":\"0xce1afc1875d364e64cfa088558ee166a441e55ef6727f38002934654ac14b25d\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Redemption.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {BytesLib} from \\\"@keep-network/bitcoin-spv-sol/contracts/BytesLib.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\nimport \\\"./Wallets.sol\\\";\\n\\nimport \\\"../bank/Bank.sol\\\";\\n\\n/// @notice Aggregates functions common to the redemption transaction proof\\n/// validation and to the moving funds transaction proof validation.\\nlibrary OutboundTx {\\n using BTCUtils for bytes;\\n\\n /// @notice Checks whether an outbound Bitcoin transaction performed from\\n /// the given wallet has an input vector that contains a single\\n /// input referring to the wallet's main UTXO. Marks that main UTXO\\n /// as correctly spent if the validation succeeds. Reverts otherwise.\\n /// There are two outbound transactions from a wallet possible: a\\n /// redemption transaction or a moving funds to another wallet\\n /// transaction.\\n /// @param walletOutboundTxInputVector Bitcoin outbound transaction's input\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVin` function\\n /// before it is passed here.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n function processWalletOutboundTxInput(\\n BridgeState.Storage storage self,\\n bytes memory walletOutboundTxInputVector,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) internal {\\n // Assert that the single outbound transaction input actually\\n // refers to the wallet's main UTXO.\\n (\\n bytes32 outpointTxHash,\\n uint32 outpointIndex\\n ) = parseWalletOutboundTxInput(walletOutboundTxInputVector);\\n require(\\n mainUtxo.txHash == outpointTxHash &&\\n mainUtxo.txOutputIndex == outpointIndex,\\n \\\"Outbound transaction input must point to the wallet's main UTXO\\\"\\n );\\n\\n // Main UTXO used as an input, mark it as spent.\\n self.spentMainUTXOs[\\n uint256(\\n keccak256(\\n abi.encodePacked(mainUtxo.txHash, mainUtxo.txOutputIndex)\\n )\\n )\\n ] = true;\\n }\\n\\n /// @notice Parses the input vector of an outbound Bitcoin transaction\\n /// performed from the given wallet. It extracts the single input\\n /// then the transaction hash and output index from its outpoint.\\n /// There are two outbound transactions from a wallet possible: a\\n /// redemption transaction or a moving funds to another wallet\\n /// transaction.\\n /// @param walletOutboundTxInputVector Bitcoin outbound transaction input\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVin` function\\n /// before it is passed here.\\n /// @return outpointTxHash 32-byte hash of the Bitcoin transaction which is\\n /// pointed in the input's outpoint.\\n /// @return outpointIndex 4-byte index of the Bitcoin transaction output\\n /// which is pointed in the input's outpoint.\\n function parseWalletOutboundTxInput(\\n bytes memory walletOutboundTxInputVector\\n ) internal pure returns (bytes32 outpointTxHash, uint32 outpointIndex) {\\n // To determine the total number of Bitcoin transaction inputs,\\n // we need to parse the compactSize uint (VarInt) the input vector is\\n // prepended by. That compactSize uint encodes the number of vector\\n // elements using the 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 `validateVin` validation.\\n // See `BitcoinTx.inputVector` docs for more details.\\n (, uint256 inputsCount) = walletOutboundTxInputVector.parseVarInt();\\n require(\\n inputsCount == 1,\\n \\\"Outbound transaction must have a single input\\\"\\n );\\n\\n bytes memory input = walletOutboundTxInputVector.extractInputAtIndex(0);\\n\\n outpointTxHash = input.extractInputTxIdLE();\\n\\n outpointIndex = BTCUtils.reverseUint32(\\n uint32(input.extractTxIndexLE())\\n );\\n\\n // There is only one input in the transaction. Input has an outpoint\\n // field that is a reference to the transaction being spent (see\\n // `BitcoinTx` docs). The outpoint contains the hash of the transaction\\n // to spend (`outpointTxHash`) and the index of the specific output\\n // from that transaction (`outpointIndex`).\\n return (outpointTxHash, outpointIndex);\\n }\\n}\\n\\n/// @title Bridge redemption\\n/// @notice The library handles the logic for redeeming Bitcoin balances from\\n/// the Bridge.\\n/// @dev To initiate a redemption, a user with a Bank balance supplies\\n/// a Bitcoin address. Then, the system calculates the redemption fee, and\\n/// releases balance to the provided Bitcoin address. Just like in case of\\n/// sweeps of revealed deposits, redemption requests are processed in\\n/// batches and require SPV proof to be submitted to the Bridge.\\nlibrary Redemption {\\n using BridgeState for BridgeState.Storage;\\n using Wallets for BridgeState.Storage;\\n using BitcoinTx for BridgeState.Storage;\\n\\n using BTCUtils for bytes;\\n using BytesLib for bytes;\\n\\n /// @notice Represents a redemption request.\\n struct RedemptionRequest {\\n // ETH address of the redeemer who created the request.\\n address redeemer;\\n // Requested TBTC amount in satoshi.\\n uint64 requestedAmount;\\n // Treasury TBTC fee in satoshi at the moment of request creation.\\n uint64 treasuryFee;\\n // Transaction maximum BTC fee in satoshi at the moment of request\\n // creation.\\n uint64 txMaxFee;\\n // UNIX timestamp the request was created at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 requestedAt;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n /// @notice Represents an outcome of the redemption Bitcoin transaction\\n /// outputs processing.\\n struct RedemptionTxOutputsInfo {\\n // Sum of all outputs values i.e. all redemptions and change value,\\n // if present.\\n uint256 outputsTotalValue;\\n // Total TBTC value in satoshi that should be burned by the Bridge.\\n // It includes the total amount of all BTC redeemed in the transaction\\n // and the fee paid to BTC miners for the redemption transaction.\\n uint64 totalBurnableValue;\\n // Total TBTC value in satoshi that should be transferred to\\n // the treasury. It is a sum of all treasury fees paid by all\\n // redeemers included in the redemption transaction.\\n uint64 totalTreasuryFee;\\n // Index of the change output. The change output becomes\\n // the new main wallet's UTXO.\\n uint32 changeIndex;\\n // Value in satoshi of the change output.\\n uint64 changeValue;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\n\\n /// @notice Represents temporary information needed during the processing of\\n /// the redemption Bitcoin transaction outputs. This structure is an\\n /// internal one and should not be exported outside of the redemption\\n /// transaction processing code.\\n /// @dev Allows to mitigate \\\"stack too deep\\\" errors on EVM.\\n struct RedemptionTxOutputsProcessingInfo {\\n // The first output starting index in the transaction.\\n uint256 outputStartingIndex;\\n // The number of outputs in the transaction.\\n uint256 outputsCount;\\n // P2PKH script for the wallet. Needed to determine the change output.\\n bytes32 walletP2PKHScriptKeccak;\\n // P2WPKH script for the wallet. Needed to determine the change output.\\n bytes32 walletP2WPKHScriptKeccak;\\n // This struct doesn't contain `__gap` property as the structure is not\\n // stored, it is used as a function's memory argument.\\n }\\n\\n event RedemptionRequested(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript,\\n address indexed redeemer,\\n uint64 requestedAmount,\\n uint64 treasuryFee,\\n uint64 txMaxFee\\n );\\n\\n event RedemptionsCompleted(\\n bytes20 indexed walletPubKeyHash,\\n bytes32 redemptionTxHash\\n );\\n\\n event RedemptionTimedOut(\\n bytes20 indexed walletPubKeyHash,\\n bytes redeemerOutputScript\\n );\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script.\\n /// This function handles the simplest case, where balance owner is\\n /// the redeemer.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed. Balance owner address is stored as\\n /// a redeemer address who will be able co claim back the Bank\\n /// balance if anything goes wrong during the redemption.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to proceed the request,\\n /// - Balance owner must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo,\\n address balanceOwner,\\n bytes calldata redeemerOutputScript,\\n uint64 amount\\n ) external {\\n requestRedemption(\\n self,\\n walletPubKeyHash,\\n mainUtxo,\\n balanceOwner,\\n balanceOwner,\\n redeemerOutputScript,\\n amount\\n );\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script. Used by\\n /// `Bridge.receiveBalanceApproval`. Can handle more complex cases\\n /// where balance owner may be someone else than the redeemer.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @param redemptionData ABI-encoded redemption data:\\n /// [\\n /// address redeemer,\\n /// bytes20 walletPubKeyHash,\\n /// bytes32 mainUtxoTxHash,\\n /// uint32 mainUtxoTxOutputIndex,\\n /// uint64 mainUtxoTxOutputValue,\\n /// bytes redeemerOutputScript\\n /// ]\\n ///\\n /// - redeemer: The Ethereum address of the redeemer who will be able\\n /// to claim Bank balance if anything goes wrong during the redemption.\\n /// In the most basic case, when someone redeems their Bitcoin\\n /// balance from the Bank, `balanceOwner` is the same as `redeemer`.\\n /// However, when a Vault is redeeming part of its balance for some\\n /// redeemer address (for example, someone who has earlier deposited\\n /// into that Vault), `balanceOwner` is the Vault, and `redeemer` is\\n /// the address for which the vault is redeeming its balance to,\\n /// - walletPubKeyHash: The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key),\\n /// - mainUtxoTxHash: Data of the wallet's main UTXO TX hash, as\\n /// currently known on the Ethereum chain,\\n /// - mainUtxoTxOutputIndex: Data of the wallet's main UTXO output\\n /// index, as currently known on Ethereum chain,\\n /// - mainUtxoTxOutputValue: Data of the wallet's main UTXO output\\n /// value, as currently known on Ethereum chain,\\n /// - redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo*` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to proceed the request,\\n /// - Balance owner must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n BridgeState.Storage storage self,\\n address balanceOwner,\\n uint64 amount,\\n bytes calldata redemptionData\\n ) external {\\n (\\n address redeemer,\\n bytes20 walletPubKeyHash,\\n bytes32 mainUtxoTxHash,\\n uint32 mainUtxoTxOutputIndex,\\n uint64 mainUtxoTxOutputValue,\\n bytes memory redeemerOutputScript\\n ) = abi.decode(\\n redemptionData,\\n (address, bytes20, bytes32, uint32, uint64, bytes)\\n );\\n\\n requestRedemption(\\n self,\\n walletPubKeyHash,\\n BitcoinTx.UTXO(\\n mainUtxoTxHash,\\n mainUtxoTxOutputIndex,\\n mainUtxoTxOutputValue\\n ),\\n balanceOwner,\\n redeemer,\\n redeemerOutputScript,\\n amount\\n );\\n }\\n\\n /// @notice Requests redemption of the given amount from the specified\\n /// wallet to the redeemer Bitcoin output script.\\n /// @param walletPubKeyHash The 20-byte wallet public key hash (computed\\n /// using Bitcoin HASH160 over the compressed ECDSA public key).\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param balanceOwner The address of the Bank balance owner whose balance\\n /// is getting redeemed.\\n /// @param redeemer The Ethereum address of the redeemer who will be able to\\n /// claim Bank balance if anything goes wrong during the redemption.\\n /// In the most basic case, when someone redeems their Bitcoin\\n /// balance from the Bank, `balanceOwner` is the same as `redeemer`.\\n /// However, when a Vault is redeeming part of its balance for some\\n /// redeemer address (for example, someone who has earlier deposited\\n /// into that Vault), `balanceOwner` is the Vault, and `redeemer` is\\n /// the address for which the vault is redeeming its balance to.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH) that will be used to lock\\n /// redeemed BTC.\\n /// @param amount Requested amount in satoshi. This is also the Bank balance\\n /// that is taken from the `balanceOwner` upon request.\\n /// Once the request is handled, the actual amount of BTC locked\\n /// on the redeemer output script will be always lower than this value\\n /// since the treasury and Bitcoin transaction fees must be incurred.\\n /// The minimal amount satisfying the request can be computed as:\\n /// `amount - (amount / redemptionTreasuryFeeDivisor) - redemptionTxMaxFee`.\\n /// Fees values are taken at the moment of request creation.\\n /// @dev Requirements:\\n /// - Wallet behind `walletPubKeyHash` must be live,\\n /// - `mainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain,\\n /// - `redeemerOutputScript` must be a proper Bitcoin script,\\n /// - `redeemerOutputScript` cannot have wallet PKH as payload,\\n /// - `amount` must be above or equal the `redemptionDustThreshold`,\\n /// - Given `walletPubKeyHash` and `redeemerOutputScript` pair can be\\n /// used for only one pending request at the same time,\\n /// - Wallet must have enough Bitcoin balance to proceed the request,\\n /// - Balance owner must make an allowance in the Bank that the Bridge\\n /// contract can spend the given `amount`.\\n function requestRedemption(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO memory mainUtxo,\\n address balanceOwner,\\n address redeemer,\\n bytes memory redeemerOutputScript,\\n uint64 amount\\n ) internal {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n bytes32 mainUtxoHash = wallet.mainUtxoHash;\\n require(\\n mainUtxoHash != bytes32(0),\\n \\\"No main UTXO for the given wallet\\\"\\n );\\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\\n // Validate if redeemer output script is a correct standard type\\n // (P2PKH, P2WPKH, P2SH or P2WSH). This is done by using\\n // `BTCUtils.extractHashAt` on it. Such a function extracts the payload\\n // properly only from standard outputs so if it succeeds, we have a\\n // guarantee the redeemer output script is proper. The underlying way\\n // of validation is the same as in tBTC v1.\\n bytes memory redeemerOutputScriptPayload = redeemerOutputScript\\n .extractHashAt(0, redeemerOutputScript.length);\\n\\n require(\\n redeemerOutputScriptPayload.length > 0,\\n \\\"Redeemer output script must be a standard type\\\"\\n );\\n // Check if the redeemer output script payload does not point to the\\n // wallet public key hash.\\n require(\\n redeemerOutputScriptPayload.length != 20 ||\\n walletPubKeyHash != redeemerOutputScriptPayload.slice20(0),\\n \\\"Redeemer output script must not point to the wallet PKH\\\"\\n );\\n\\n require(\\n amount >= self.redemptionDustThreshold,\\n \\\"Redemption amount too small\\\"\\n );\\n\\n // The redemption key is built on top of the wallet public key hash\\n // and redeemer output script pair. That means there can be only one\\n // request asking for redemption from the given wallet to the given\\n // BTC script at the same time.\\n uint256 redemptionKey = getRedemptionKey(\\n walletPubKeyHash,\\n redeemerOutputScript\\n );\\n\\n // Check if given redemption key is not used by a pending redemption.\\n // There is no need to check for existence in `timedOutRedemptions`\\n // since the wallet's state is changed to other than Live after\\n // first time out is reported so making new requests is not possible.\\n // slither-disable-next-line incorrect-equality\\n require(\\n self.pendingRedemptions[redemptionKey].requestedAt == 0,\\n \\\"There is a pending redemption request from this wallet to the same address\\\"\\n );\\n\\n // No need to check whether `amount - treasuryFee - txMaxFee > 0`\\n // since the `redemptionDustThreshold` should force that condition\\n // to be always true.\\n uint64 treasuryFee = self.redemptionTreasuryFeeDivisor > 0\\n ? amount / self.redemptionTreasuryFeeDivisor\\n : 0;\\n uint64 txMaxFee = self.redemptionTxMaxFee;\\n\\n // The main wallet UTXO's value doesn't include all pending redemptions.\\n // To determine if the requested redemption can be performed by the\\n // wallet we need to subtract the total value of all pending redemptions\\n // from that wallet's main UTXO value. Given that the treasury fee is\\n // not redeemed from the wallet, we are subtracting it.\\n wallet.pendingRedemptionsValue += amount - treasuryFee;\\n require(\\n mainUtxo.txOutputValue >= wallet.pendingRedemptionsValue,\\n \\\"Insufficient wallet funds\\\"\\n );\\n\\n self.pendingRedemptions[redemptionKey] = RedemptionRequest(\\n redeemer,\\n amount,\\n treasuryFee,\\n txMaxFee,\\n /* solhint-disable-next-line not-rely-on-time */\\n uint32(block.timestamp)\\n );\\n\\n // slither-disable-next-line reentrancy-events\\n emit RedemptionRequested(\\n walletPubKeyHash,\\n redeemerOutputScript,\\n redeemer,\\n amount,\\n treasuryFee,\\n txMaxFee\\n );\\n\\n self.bank.transferBalanceFrom(balanceOwner, address(this), amount);\\n }\\n\\n /// @notice Used by the wallet to prove the BTC redemption transaction\\n /// and to make the necessary bookkeeping. Redemption is only\\n /// accepted if it satisfies SPV proof.\\n ///\\n /// The function is performing Bank balance updates by burning\\n /// the total redeemed Bitcoin amount from Bridge balance and\\n /// transferring the treasury fee sum to the treasury address.\\n ///\\n /// It is possible to prove the given redemption only one time.\\n /// @param redemptionTx Bitcoin redemption transaction data.\\n /// @param redemptionProof Bitcoin redemption proof data.\\n /// @param mainUtxo Data of the wallet's main UTXO, as currently known on\\n /// the Ethereum chain.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @dev Requirements:\\n /// - `redemptionTx` 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 `redemptionTx` should represent a Bitcoin transaction with\\n /// exactly 1 input that refers to the wallet's main UTXO. That\\n /// transaction should have 1..n outputs handling existing pending\\n /// redemption requests or pointing to reported timed out requests.\\n /// There can be also 1 optional output representing the\\n /// change and pointing back to the 20-byte wallet public key hash.\\n /// The change should be always present if the redeemed value sum\\n /// is lower than the total wallet's BTC balance,\\n /// - `redemptionProof` components must match the expected structure.\\n /// See `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 /// Additionally, the recent main UTXO on Ethereum must be set,\\n /// - `walletPubKeyHash` must be connected with the main UTXO used\\n /// as transaction single input.\\n /// Other remarks:\\n /// - Putting the change output as the first transaction output can\\n /// save some gas because the output processing loop begins each\\n /// iteration by checking whether the given output is the change\\n /// thus uses some gas for making the comparison. Once the change\\n /// is identified, that check is omitted in further iterations.\\n function submitRedemptionProof(\\n BridgeState.Storage storage self,\\n BitcoinTx.Info calldata redemptionTx,\\n BitcoinTx.Proof calldata redemptionProof,\\n BitcoinTx.UTXO calldata mainUtxo,\\n bytes20 walletPubKeyHash\\n ) external {\\n // Wallet state validation is performed in the `resolveRedeemingWallet`\\n // function.\\n\\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 redemptionTxHash = self.validateProof(\\n redemptionTx,\\n redemptionProof\\n );\\n\\n Wallets.Wallet storage wallet = resolveRedeemingWallet(\\n self,\\n walletPubKeyHash,\\n mainUtxo\\n );\\n\\n // Process the redemption transaction input. Specifically, check if it\\n // refers to the expected wallet's main UTXO.\\n OutboundTx.processWalletOutboundTxInput(\\n self,\\n redemptionTx.inputVector,\\n mainUtxo\\n );\\n\\n // Process redemption transaction outputs to extract some info required\\n // for further processing.\\n RedemptionTxOutputsInfo memory outputsInfo = processRedemptionTxOutputs(\\n self,\\n redemptionTx.outputVector,\\n walletPubKeyHash\\n );\\n\\n require(\\n mainUtxo.txOutputValue - outputsInfo.outputsTotalValue <=\\n self.redemptionTxMaxTotalFee,\\n \\\"Transaction fee is too high\\\"\\n );\\n\\n if (outputsInfo.changeValue > 0) {\\n // If the change value is grater than zero, it means the change\\n // output exists and can be used as new wallet's main UTXO.\\n wallet.mainUtxoHash = keccak256(\\n abi.encodePacked(\\n redemptionTxHash,\\n outputsInfo.changeIndex,\\n outputsInfo.changeValue\\n )\\n );\\n } else {\\n // If the change value is zero, it means the change output doesn't\\n // exists and no funds left on the wallet. Delete the main UTXO\\n // for that wallet to represent that state in a proper way.\\n delete wallet.mainUtxoHash;\\n }\\n\\n wallet.pendingRedemptionsValue -= outputsInfo.totalBurnableValue;\\n\\n emit RedemptionsCompleted(walletPubKeyHash, redemptionTxHash);\\n\\n self.bank.decreaseBalance(outputsInfo.totalBurnableValue);\\n\\n if (outputsInfo.totalTreasuryFee > 0) {\\n self.bank.transferBalance(\\n self.treasury,\\n outputsInfo.totalTreasuryFee\\n );\\n }\\n }\\n\\n /// @notice Resolves redeeming wallet based on the provided wallet public\\n /// key 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.\\n /// @return wallet Data of the sweeping wallet.\\n /// @dev Requirements:\\n /// - Sweeping wallet must be either in Live or MovingFunds state,\\n /// - Main UTXO of the redeeming wallet must exists in the storage,\\n /// - The passed `mainUTXO` parameter must be equal to the stored one.\\n function resolveRedeemingWallet(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata mainUtxo\\n ) internal view returns (Wallets.Wallet storage wallet) {\\n wallet = self.registeredWallets[walletPubKeyHash];\\n\\n // Assert that main UTXO for passed wallet exists in storage.\\n bytes32 mainUtxoHash = wallet.mainUtxoHash;\\n require(mainUtxoHash != bytes32(0), \\\"No main UTXO for given wallet\\\");\\n\\n // Assert that passed main UTXO parameter is the same as in storage and\\n // can be used for further processing.\\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\\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\\n /// @notice Processes the Bitcoin redemption transaction output vector.\\n /// It extracts each output and tries to identify it as a pending\\n /// redemption request, reported timed out request, or change.\\n /// Reverts if one of the outputs cannot be recognized properly.\\n /// This function also marks each request as processed by removing\\n /// them from `pendingRedemptions` mapping.\\n /// @param redemptionTxOutputVector Bitcoin redemption transaction output\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVout` function\\n /// before it is passed here.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @return info Outcomes of the processing.\\n function processRedemptionTxOutputs(\\n BridgeState.Storage storage self,\\n bytes memory redemptionTxOutputVector,\\n bytes20 walletPubKeyHash\\n ) internal returns (RedemptionTxOutputsInfo memory info) {\\n // Determining the total number of redemption transaction outputs in\\n // the same way as for number of inputs. See `BitcoinTx.outputVector`\\n // docs for more details.\\n (\\n uint256 outputsCompactSizeUintLength,\\n uint256 outputsCount\\n ) = redemptionTxOutputVector.parseVarInt();\\n\\n // To determine the first output starting index, we must jump over\\n // the compactSize uint which prepends the output 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 outputStartingIndex = 1 + outputsCompactSizeUintLength;\\n\\n // Calculate the keccak256 for two possible wallet's P2PKH or P2WPKH\\n // scripts that can be used to lock the change. This is done upfront to\\n // save on gas. Both scripts have a strict format defined by Bitcoin.\\n //\\n // The P2PKH script has the byte format: <0x1976a914> <20-byte PKH> <0x88ac>.\\n // According to 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 walletP2PKHScriptKeccak = keccak256(\\n abi.encodePacked(BitcoinTx.makeP2PKHScript(walletPubKeyHash))\\n );\\n // The P2WPKH script has the byte format: <0x160014> <20-byte PKH>.\\n // According to 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 walletP2WPKHScriptKeccak = keccak256(\\n abi.encodePacked(BitcoinTx.makeP2WPKHScript(walletPubKeyHash))\\n );\\n\\n return\\n processRedemptionTxOutputs(\\n self,\\n redemptionTxOutputVector,\\n walletPubKeyHash,\\n RedemptionTxOutputsProcessingInfo(\\n outputStartingIndex,\\n outputsCount,\\n walletP2PKHScriptKeccak,\\n walletP2WPKHScriptKeccak\\n )\\n );\\n }\\n\\n /// @notice Processes all outputs from the redemption transaction. Tries to\\n /// identify output as a change output, pending redemption request\\n /// or reported redemption. Reverts if one of the outputs cannot be\\n /// recognized properly. Marks each request as processed by removing\\n /// them from `pendingRedemptions` mapping.\\n /// @param redemptionTxOutputVector Bitcoin redemption transaction output\\n /// vector. This function assumes vector's structure is valid so it\\n /// must be validated using e.g. `BTCUtils.validateVout` function\\n /// before it is passed here.\\n /// @param walletPubKeyHash 20-byte public key hash (computed using Bitcoin\\n /// HASH160 over the compressed ECDSA public key) of the wallet which\\n /// performed the redemption transaction.\\n /// @param processInfo RedemptionTxOutputsProcessingInfo identifying output\\n /// starting index, the number of outputs and possible wallet change\\n /// P2PKH and P2WPKH scripts.\\n function processRedemptionTxOutputs(\\n BridgeState.Storage storage self,\\n bytes memory redemptionTxOutputVector,\\n bytes20 walletPubKeyHash,\\n RedemptionTxOutputsProcessingInfo memory processInfo\\n ) internal returns (RedemptionTxOutputsInfo memory resultInfo) {\\n // Helper flag indicating whether there was at least one redemption\\n // output present (redemption must be either pending or reported as\\n // timed out).\\n bool redemptionPresent = false;\\n\\n // Outputs processing loop.\\n for (uint256 i = 0; i < processInfo.outputsCount; i++) {\\n uint256 outputLength = redemptionTxOutputVector\\n .determineOutputLengthAt(processInfo.outputStartingIndex);\\n\\n // Extract the value from given output.\\n uint64 outputValue = redemptionTxOutputVector.extractValueAt(\\n processInfo.outputStartingIndex\\n );\\n\\n // The output consists of an 8-byte value and a variable length\\n // script. To hash that script we slice the output starting from\\n // 9th byte until the end.\\n uint256 scriptLength = outputLength - 8;\\n uint256 outputScriptStart = processInfo.outputStartingIndex + 8;\\n\\n bytes32 outputScriptHash;\\n /* solhint-disable-next-line no-inline-assembly */\\n assembly {\\n // The first argument to assembly keccak256 is the pointer.\\n // We point to `redemptionTxOutputVector` but at the position\\n // indicated by `outputScriptStart`. To load that position, we\\n // need to call `add(outputScriptStart, 32)` because\\n // `outputScriptStart` has 32 bytes.\\n outputScriptHash := keccak256(\\n add(redemptionTxOutputVector, add(outputScriptStart, 32)),\\n scriptLength\\n )\\n }\\n\\n if (\\n resultInfo.changeValue == 0 &&\\n (outputScriptHash == processInfo.walletP2PKHScriptKeccak ||\\n outputScriptHash == processInfo.walletP2WPKHScriptKeccak) &&\\n outputValue > 0\\n ) {\\n // If we entered here, that means the change output with a\\n // proper non-zero value was found.\\n resultInfo.changeIndex = uint32(i);\\n resultInfo.changeValue = outputValue;\\n } else {\\n // If we entered here, that the means the given output is\\n // supposed to represent a redemption.\\n (\\n uint64 burnableValue,\\n uint64 treasuryFee\\n ) = processNonChangeRedemptionTxOutput(\\n self,\\n _getRedemptionKey(walletPubKeyHash, outputScriptHash),\\n outputValue\\n );\\n resultInfo.totalBurnableValue += burnableValue;\\n resultInfo.totalTreasuryFee += treasuryFee;\\n redemptionPresent = true;\\n }\\n\\n resultInfo.outputsTotalValue += outputValue;\\n\\n // Make the `outputStartingIndex` pointing to the next output by\\n // increasing it by current output's length.\\n processInfo.outputStartingIndex += outputLength;\\n }\\n\\n // Protect against the cases when there is only a single change output\\n // referring back to the wallet PKH and just burning main UTXO value\\n // for transaction fees.\\n require(\\n redemptionPresent,\\n \\\"Redemption transaction must process at least one redemption\\\"\\n );\\n }\\n\\n /// @notice Processes a single redemption transaction output. Tries to\\n /// identify output as a pending redemption request or reported\\n /// redemption timeout. Output script passed to this function must\\n /// not be the change output. Such output needs to be identified\\n /// separately before calling this function.\\n /// Reverts if output is neither requested pending redemption nor\\n /// requested and reported timed-out redemption.\\n /// This function also marks each pending request as processed by\\n /// removing them from `pendingRedemptions` mapping.\\n /// @param redemptionKey Redemption key of the output being processed.\\n /// @param outputValue Value of the output being processed.\\n /// @return burnableValue The value burnable as a result of processing this\\n /// single redemption output. This value needs to be summed up with\\n /// burnable values of all other outputs to evaluate total burnable\\n /// value for the entire redemption transaction. This value is 0\\n /// for a timed-out redemption request.\\n /// @return treasuryFee The treasury fee from this single redemption output.\\n /// This value needs to be summed up with treasury fees of all other\\n /// outputs to evaluate the total treasury fee for the entire\\n /// redemption transaction. This value is 0 for a timed-out\\n /// redemption request.\\n /// @dev Requirements:\\n /// - This function should be called only if the given output\\n /// represents redemption. It must not be the change output.\\n function processNonChangeRedemptionTxOutput(\\n BridgeState.Storage storage self,\\n uint256 redemptionKey,\\n uint64 outputValue\\n ) internal returns (uint64 burnableValue, uint64 treasuryFee) {\\n if (self.pendingRedemptions[redemptionKey].requestedAt != 0) {\\n // If we entered here, that means the output was identified\\n // as a pending redemption request.\\n RedemptionRequest storage request = self.pendingRedemptions[\\n redemptionKey\\n ];\\n // Compute the request's redeemable amount as the requested\\n // amount reduced by the treasury fee. The request's\\n // minimal amount is then the redeemable amount reduced by\\n // the maximum transaction fee.\\n uint64 redeemableAmount = request.requestedAmount -\\n request.treasuryFee;\\n // Output value must fit between the request's redeemable\\n // and minimal amounts to be deemed valid.\\n require(\\n redeemableAmount - request.txMaxFee <= outputValue &&\\n outputValue <= redeemableAmount,\\n \\\"Output value is not within the acceptable range of the pending request\\\"\\n );\\n // Add the redeemable amount to the total burnable value\\n // the Bridge will use to decrease its balance in the Bank.\\n burnableValue = redeemableAmount;\\n // Add the request's treasury fee to the total treasury fee\\n // value the Bridge will transfer to the treasury.\\n treasuryFee = request.treasuryFee;\\n // Request was properly handled so remove its redemption\\n // key from the mapping to make it reusable for further\\n // requests.\\n delete self.pendingRedemptions[redemptionKey];\\n } else {\\n // If we entered here, the output is not a redemption\\n // request but there is still a chance the given output is\\n // related to a reported timed out redemption request.\\n // If so, check if the output value matches the request\\n // amount to confirm this is an overdue request fulfillment\\n // then bypass this output and process the subsequent\\n // ones. That also means the wallet was already punished\\n // for the inactivity. Otherwise, just revert.\\n RedemptionRequest storage request = self.timedOutRedemptions[\\n redemptionKey\\n ];\\n\\n require(\\n request.requestedAt != 0,\\n \\\"Output is a non-requested redemption\\\"\\n );\\n\\n uint64 redeemableAmount = request.requestedAmount -\\n request.treasuryFee;\\n\\n require(\\n redeemableAmount - request.txMaxFee <= outputValue &&\\n outputValue <= redeemableAmount,\\n \\\"Output value is not within the acceptable range of the timed out request\\\"\\n );\\n\\n delete self.timedOutRedemptions[redemptionKey];\\n }\\n }\\n\\n /// @notice Notifies that there is a pending redemption request associated\\n /// with the given wallet, that has timed out. The redemption\\n /// request is identified by the key built as\\n /// `keccak256(keccak256(redeemerOutputScript) | walletPubKeyHash)`.\\n /// The results of calling this function:\\n /// - the pending redemptions value for the wallet will be decreased\\n /// by the requested amount (minus treasury fee),\\n /// - the tokens taken from the redeemer on redemption request will\\n /// be returned to the redeemer,\\n /// - the request will be moved from pending redemptions to\\n /// timed-out redemptions,\\n /// - if the state of the wallet is `Live` or `MovingFunds`, the\\n /// wallet operators will be slashed and the notifier will be\\n /// rewarded,\\n /// - if the state of wallet is `Live`, the wallet will be closed or\\n /// marked as `MovingFunds` (depending on the presence or absence\\n /// of the wallet's main UTXO) and the wallet will no longer be\\n /// marked as the active wallet (if it was marked as such).\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param redeemerOutputScript The redeemer's length-prefixed output\\n /// script (P2PKH, P2WPKH, P2SH or P2WSH).\\n /// @dev Requirements:\\n /// - The wallet must be in the Live or MovingFunds or Terminated state,\\n /// - The redemption request identified by `walletPubKeyHash` and\\n /// `redeemerOutputScript` must exist,\\n /// - The expression `keccak256(abi.encode(walletMembersIDs))` must\\n /// be exactly the same as the hash stored under `membersIdsHash`\\n /// for the given `walletID`. Those IDs are not directly stored\\n /// in the contract for gas efficiency purposes but they can be\\n /// read from appropriate `DkgResultSubmitted` and `DkgResultApproved`\\n /// events of the `WalletRegistry` contract,\\n /// - The amount of time defined by `redemptionTimeout` must have\\n /// passed since the redemption was requested (the request must be\\n /// timed-out).\\n function notifyRedemptionTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs,\\n bytes calldata redeemerOutputScript\\n ) external {\\n // Wallet state is validated in `notifyWalletRedemptionTimeout`.\\n uint256 redemptionKey = getRedemptionKey(\\n walletPubKeyHash,\\n redeemerOutputScript\\n );\\n Redemption.RedemptionRequest memory request = self.pendingRedemptions[\\n redemptionKey\\n ];\\n\\n require(request.requestedAt > 0, \\\"Redemption request does not exist\\\");\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n request.requestedAt + self.redemptionTimeout < block.timestamp,\\n \\\"Redemption request has not timed out\\\"\\n );\\n\\n // Update the wallet's pending redemptions value\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n wallet.pendingRedemptionsValue -=\\n request.requestedAmount -\\n request.treasuryFee;\\n\\n // It is worth noting that there is no need to check if\\n // `timedOutRedemption` mapping already contains the given redemption\\n // key. There is no possibility to re-use a key of a reported timed-out\\n // redemption because the wallet responsible for causing the timeout is\\n // moved to a state that prevents it to receive new redemption requests.\\n\\n // Propagate timeout consequences to the wallet\\n self.notifyWalletRedemptionTimeout(walletPubKeyHash, walletMembersIDs);\\n\\n // Move the redemption from pending redemptions to timed-out redemptions\\n self.timedOutRedemptions[redemptionKey] = request;\\n delete self.pendingRedemptions[redemptionKey];\\n\\n // slither-disable-next-line reentrancy-events\\n emit RedemptionTimedOut(walletPubKeyHash, redeemerOutputScript);\\n\\n // Return the requested amount of tokens to the redeemer\\n self.bank.transferBalance(request.redeemer, request.requestedAmount);\\n }\\n\\n /// @notice Calculate redemption key without allocations.\\n /// @param walletPubKeyHash the pubkey hash of the wallet.\\n /// @param script the output script of the redemption.\\n /// @return The key = keccak256(keccak256(script) | walletPubKeyHash).\\n function getRedemptionKey(bytes20 walletPubKeyHash, bytes memory script)\\n internal\\n pure\\n returns (uint256)\\n {\\n bytes32 scriptHash = keccak256(script);\\n uint256 key;\\n /* solhint-disable-next-line no-inline-assembly */\\n assembly {\\n mstore(0, scriptHash)\\n mstore(32, walletPubKeyHash)\\n key := keccak256(0, 52)\\n }\\n return key;\\n }\\n\\n /// @notice Finish calculating redemption key without allocations.\\n /// @param walletPubKeyHash the pubkey hash of the wallet.\\n /// @param scriptHash the output script hash of the redemption.\\n /// @return The key = keccak256(scriptHash | walletPubKeyHash).\\n function _getRedemptionKey(bytes20 walletPubKeyHash, bytes32 scriptHash)\\n internal\\n pure\\n returns (uint256)\\n {\\n uint256 key;\\n /* solhint-disable-next-line no-inline-assembly */\\n assembly {\\n mstore(0, scriptHash)\\n mstore(32, walletPubKeyHash)\\n key := keccak256(0, 52)\\n }\\n return key;\\n }\\n}\\n\",\"keccak256\":\"0x341e2636230764f1bcc75948adedd4c8b38e34a87bb9094b251f9b0870922e93\",\"license\":\"GPL-3.0-only\"},\"contracts/bridge/Wallets.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport {BTCUtils} from \\\"@keep-network/bitcoin-spv-sol/contracts/BTCUtils.sol\\\";\\nimport {EcdsaDkg} from \\\"@keep-network/ecdsa/contracts/libraries/EcdsaDkg.sol\\\";\\nimport {Math} from \\\"@openzeppelin/contracts/utils/math/Math.sol\\\";\\n\\nimport \\\"./BitcoinTx.sol\\\";\\nimport \\\"./EcdsaLib.sol\\\";\\nimport \\\"./BridgeState.sol\\\";\\n\\n/// @title Wallet library\\n/// @notice Library responsible for handling integration between Bridge\\n/// contract and ECDSA wallets.\\nlibrary Wallets {\\n using BTCUtils for bytes;\\n\\n /// @notice Represents wallet state:\\n enum WalletState {\\n /// @dev The wallet is unknown to the Bridge.\\n Unknown,\\n /// @dev The wallet can sweep deposits and accept redemption requests.\\n Live,\\n /// @dev The wallet was deemed unhealthy and is expected to move their\\n /// outstanding funds to another wallet. The wallet can still\\n /// fulfill their pending redemption requests although new\\n /// redemption requests and new deposit reveals are not accepted.\\n MovingFunds,\\n /// @dev The wallet moved or redeemed all their funds and is in the\\n /// closing period where it is still a subject of fraud challenges\\n /// and must defend against them. This state is needed to protect\\n /// against deposit frauds on deposits revealed but not swept.\\n /// The closing period must be greater that the deposit refund\\n /// time plus some time margin.\\n Closing,\\n /// @dev The wallet finalized the closing period successfully and\\n /// can no longer perform any action in the Bridge.\\n Closed,\\n /// @dev The wallet committed a fraud that was reported, did not move\\n /// funds to another wallet before a timeout, or did not sweep\\n /// funds moved to if from another wallet before a timeout. The\\n /// wallet is blocked and can not perform any actions in the Bridge.\\n /// Off-chain coordination with the wallet operators is needed to\\n /// recover funds.\\n Terminated\\n }\\n\\n /// @notice Holds information about a wallet.\\n struct Wallet {\\n // Identifier of a ECDSA Wallet registered in the ECDSA Wallet Registry.\\n bytes32 ecdsaWalletID;\\n // Latest wallet's main UTXO hash computed as\\n // keccak256(txHash | txOutputIndex | txOutputValue). The `tx` prefix\\n // refers to the transaction which created that main UTXO. The `txHash`\\n // is `bytes32` (ordered as in Bitcoin internally), `txOutputIndex`\\n // an `uint32`, and `txOutputValue` an `uint64` value.\\n bytes32 mainUtxoHash;\\n // The total redeemable value of pending redemption requests targeting\\n // that wallet.\\n uint64 pendingRedemptionsValue;\\n // UNIX timestamp the wallet was created at.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 createdAt;\\n // UNIX timestamp indicating the moment the wallet was requested to\\n // move their funds.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 movingFundsRequestedAt;\\n // UNIX timestamp indicating the moment the wallet's closing period\\n // started.\\n // XXX: Unsigned 32-bit int unix seconds, will break February 7th 2106.\\n uint32 closingStartedAt;\\n // Total count of pending moved funds sweep requests targeting this wallet.\\n uint32 pendingMovedFundsSweepRequestsCount;\\n // Current state of the wallet.\\n WalletState state;\\n // Moving funds target wallet commitment submitted by the wallet. It\\n // is built by applying the keccak256 hash over the list of 20-byte\\n // public key hashes of the target wallets.\\n bytes32 movingFundsTargetWalletsCommitmentHash;\\n // This struct doesn't contain `__gap` property as the structure is stored\\n // in a mapping, mappings store values in different slots and they are\\n // not contiguous with other values.\\n }\\n\\n event NewWalletRequested();\\n\\n event NewWalletRegistered(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletMovingFunds(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosing(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletClosed(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n event WalletTerminated(\\n bytes32 indexed ecdsaWalletID,\\n bytes20 indexed walletPubKeyHash\\n );\\n\\n /// @notice Requests creation of a new wallet. This function just\\n /// forms a request and the creation process is performed\\n /// asynchronously. Outcome of that process should be delivered\\n /// using `registerNewWallet` function.\\n /// @param activeWalletMainUtxo Data of the active wallet's main UTXO, as\\n /// currently known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - `activeWalletMainUtxo` components must point to the recent main\\n /// UTXO of the given active wallet, as currently known on the\\n /// Ethereum chain. If there is no active wallet at the moment, or\\n /// the active wallet has no main UTXO, this parameter can be\\n /// empty as it is ignored,\\n /// - Wallet creation must not be in progress,\\n /// - If the active wallet is set, one of the following\\n /// conditions must be true:\\n /// - The active wallet BTC balance is above the minimum threshold\\n /// and the active wallet is old enough, i.e. the creation period\\n /// was elapsed since its creation time,\\n /// - The active wallet BTC balance is above the maximum threshold.\\n function requestNewWallet(\\n BridgeState.Storage storage self,\\n BitcoinTx.UTXO calldata activeWalletMainUtxo\\n ) external {\\n require(\\n self.ecdsaWalletRegistry.getWalletCreationState() ==\\n EcdsaDkg.State.IDLE,\\n \\\"Wallet creation already in progress\\\"\\n );\\n\\n bytes20 activeWalletPubKeyHash = self.activeWalletPubKeyHash;\\n\\n // If the active wallet is set, fetch this wallet's details from\\n // storage to perform conditions check. The `registerNewWallet`\\n // function guarantees an active wallet is always one of the\\n // registered ones.\\n if (activeWalletPubKeyHash != bytes20(0)) {\\n uint64 activeWalletBtcBalance = getWalletBtcBalance(\\n self,\\n activeWalletPubKeyHash,\\n activeWalletMainUtxo\\n );\\n uint32 activeWalletCreatedAt = self\\n .registeredWallets[activeWalletPubKeyHash]\\n .createdAt;\\n /* solhint-disable-next-line not-rely-on-time */\\n bool activeWalletOldEnough = block.timestamp >=\\n activeWalletCreatedAt + self.walletCreationPeriod;\\n\\n require(\\n (activeWalletOldEnough &&\\n activeWalletBtcBalance >=\\n self.walletCreationMinBtcBalance) ||\\n activeWalletBtcBalance >= self.walletCreationMaxBtcBalance,\\n \\\"Wallet creation conditions are not met\\\"\\n );\\n }\\n\\n emit NewWalletRequested();\\n\\n self.ecdsaWalletRegistry.requestNewWallet();\\n }\\n\\n /// @notice Registers a new wallet. This function should be called\\n /// after the wallet creation process initiated using\\n /// `requestNewWallet` completes and brings the outcomes.\\n /// @param ecdsaWalletID Wallet's unique identifier.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Given wallet data must not belong to an already registered wallet.\\n function registerNewWallet(\\n BridgeState.Storage storage self,\\n bytes32 ecdsaWalletID,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external {\\n require(\\n msg.sender == address(self.ecdsaWalletRegistry),\\n \\\"Caller is not the ECDSA Wallet Registry\\\"\\n );\\n\\n // Compress wallet's public key and calculate Bitcoin's hash160 of it.\\n bytes20 walletPubKeyHash = bytes20(\\n EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()\\n );\\n\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n require(\\n wallet.state == WalletState.Unknown,\\n \\\"ECDSA wallet has been already registered\\\"\\n );\\n wallet.ecdsaWalletID = ecdsaWalletID;\\n wallet.state = WalletState.Live;\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.createdAt = uint32(block.timestamp);\\n\\n // Set the freshly created wallet as the new active wallet.\\n self.activeWalletPubKeyHash = walletPubKeyHash;\\n\\n self.liveWalletsCount++;\\n\\n emit NewWalletRegistered(ecdsaWalletID, walletPubKeyHash);\\n }\\n\\n /// @notice Handles a notification about a wallet redemption timeout.\\n /// Triggers the wallet moving funds process only if the wallet is\\n /// still in the Live state. That means multiple action timeouts can\\n /// be reported for the same wallet but only the first report\\n /// requests the wallet to move their funds. Executes slashing if\\n /// the wallet is in Live or MovingFunds state. Allows to notify\\n /// redemption timeout also for a Terminated wallet in case the\\n /// redemption was requested before the wallet got terminated.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the `Live`, `MovingFunds`,\\n /// or `Terminated` state.\\n function notifyWalletRedemptionTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n WalletState walletState = wallet.state;\\n\\n require(\\n walletState == WalletState.Live ||\\n walletState == WalletState.MovingFunds ||\\n walletState == WalletState.Terminated,\\n \\\"Wallet must be in Live or MovingFunds or Terminated state\\\"\\n );\\n\\n if (\\n walletState == Wallets.WalletState.Live ||\\n walletState == Wallets.WalletState.MovingFunds\\n ) {\\n // Slash the wallet operators and reward the notifier\\n self.ecdsaWalletRegistry.seize(\\n self.redemptionTimeoutSlashingAmount,\\n self.redemptionTimeoutNotifierRewardMultiplier,\\n msg.sender,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n }\\n\\n if (walletState == WalletState.Live) {\\n moveFunds(self, walletPubKeyHash);\\n }\\n }\\n\\n /// @notice Handles a notification about a wallet heartbeat failure and\\n /// triggers the wallet moving funds process.\\n /// @param publicKeyX Wallet's public key's X coordinate.\\n /// @param publicKeyY Wallet's public key's Y coordinate.\\n /// @dev Requirements:\\n /// - The only caller authorized to call this function is `registry`,\\n /// - Wallet must be in Live state.\\n function notifyWalletHeartbeatFailed(\\n BridgeState.Storage storage self,\\n bytes32 publicKeyX,\\n bytes32 publicKeyY\\n ) external {\\n require(\\n msg.sender == address(self.ecdsaWalletRegistry),\\n \\\"Caller is not the ECDSA Wallet Registry\\\"\\n );\\n\\n // Compress wallet's public key and calculate Bitcoin's hash160 of it.\\n bytes20 walletPubKeyHash = bytes20(\\n EcdsaLib.compressPublicKey(publicKeyX, publicKeyY).hash160View()\\n );\\n\\n require(\\n self.registeredWallets[walletPubKeyHash].state == WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n moveFunds(self, walletPubKeyHash);\\n }\\n\\n /// @notice Notifies that the wallet is either old enough or has too few\\n /// satoshis left and qualifies to be closed.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\n /// @dev Requirements:\\n /// - Wallet must not be set as the current active wallet,\\n /// - Wallet must exceed the wallet maximum age OR the wallet BTC\\n /// balance must be lesser than the minimum threshold. If the latter\\n /// case is true, the `walletMainUtxo` components must point to the\\n /// recent main UTXO of the given wallet, as currently known on the\\n /// Ethereum chain. If the wallet has no main UTXO, this parameter\\n /// can be empty as it is ignored since the wallet balance is\\n /// assumed to be zero,\\n /// - Wallet must be in Live state.\\n function notifyWalletCloseable(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo\\n ) external {\\n require(\\n self.activeWalletPubKeyHash != walletPubKeyHash,\\n \\\"Active wallet cannot be considered closeable\\\"\\n );\\n\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n require(\\n wallet.state == WalletState.Live,\\n \\\"Wallet must be in Live state\\\"\\n );\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n bool walletOldEnough = block.timestamp >=\\n wallet.createdAt + self.walletMaxAge;\\n\\n require(\\n walletOldEnough ||\\n getWalletBtcBalance(self, walletPubKeyHash, walletMainUtxo) <\\n self.walletClosureMinBtcBalance,\\n \\\"Wallet needs to be old enough or have too few satoshis\\\"\\n );\\n\\n moveFunds(self, walletPubKeyHash);\\n }\\n\\n /// @notice Notifies about the end of the closing period for the given wallet.\\n /// Closes the wallet ultimately and notifies the ECDSA registry\\n /// about this fact.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the Closing state,\\n /// - The wallet closing period must have elapsed.\\n function notifyWalletClosingPeriodElapsed(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n require(\\n wallet.state == WalletState.Closing,\\n \\\"Wallet must be in Closing state\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp >\\n wallet.closingStartedAt + self.walletClosingPeriod,\\n \\\"Closing period has not elapsed yet\\\"\\n );\\n\\n finalizeWalletClosing(self, walletPubKeyHash);\\n }\\n\\n /// @notice Notifies that the wallet completed the moving funds process\\n /// successfully. Checks if the funds were moved to the expected\\n /// target wallets. Closes the source wallet if everything went\\n /// good and reverts otherwise.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param targetWalletsHash 32-byte keccak256 hash over the list of\\n /// 20-byte public key hashes of the target wallets actually used\\n /// within the moving funds transactions.\\n /// @dev Requirements:\\n /// - The caller must make sure the moving funds transaction actually\\n /// happened on Bitcoin chain and fits the protocol requirements,\\n /// - The source wallet must be in the MovingFunds state,\\n /// - The target wallets commitment must be submitted by the source\\n /// wallet,\\n /// - The actual target wallets used in the moving funds transaction\\n /// must be exactly the same as the target wallets commitment.\\n function notifyWalletFundsMoved(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n bytes32 targetWalletsHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n // Check that the wallet is in the MovingFunds state but don't check\\n // if the moving funds timeout is exceeded. That should give a\\n // possibility to move funds in case when timeout was hit but was\\n // not reported yet.\\n require(\\n wallet.state == WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n bytes32 targetWalletsCommitmentHash = wallet\\n .movingFundsTargetWalletsCommitmentHash;\\n\\n require(\\n targetWalletsCommitmentHash != bytes32(0),\\n \\\"Target wallets commitment not submitted yet\\\"\\n );\\n\\n // Make sure that the target wallets where funds were moved to are\\n // exactly the same as the ones the source wallet committed to.\\n require(\\n targetWalletsCommitmentHash == targetWalletsHash,\\n \\\"Target wallets don't correspond to the commitment\\\"\\n );\\n\\n // If funds were moved, the wallet has no longer a main UTXO.\\n delete wallet.mainUtxoHash;\\n\\n beginWalletClosing(self, walletPubKeyHash);\\n }\\n\\n /// @notice Called when a MovingFunds wallet has a balance below the dust\\n /// threshold. Begins the wallet closing.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state.\\n function notifyWalletMovingFundsBelowDust(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n WalletState walletState = self\\n .registeredWallets[walletPubKeyHash]\\n .state;\\n\\n require(\\n walletState == Wallets.WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n beginWalletClosing(self, walletPubKeyHash);\\n }\\n\\n /// @notice Called when the timeout for MovingFunds for the wallet elapsed.\\n /// Slashes wallet members and terminates the wallet.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the MovingFunds state.\\n function notifyWalletMovingFundsTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) internal {\\n Wallets.Wallet storage wallet = self.registeredWallets[\\n walletPubKeyHash\\n ];\\n\\n require(\\n wallet.state == Wallets.WalletState.MovingFunds,\\n \\\"Wallet must be in MovingFunds state\\\"\\n );\\n\\n self.ecdsaWalletRegistry.seize(\\n self.movingFundsTimeoutSlashingAmount,\\n self.movingFundsTimeoutNotifierRewardMultiplier,\\n msg.sender,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n\\n terminateWallet(self, walletPubKeyHash);\\n }\\n\\n /// @notice Called when a wallet which was asked to sweep funds moved from\\n /// another wallet did not provide a sweeping proof before a timeout.\\n /// Slashes and terminates the wallet who failed to provide a proof.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet which was\\n /// supposed to sweep funds.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @dev Requirements:\\n /// - The wallet must be in the `Live`, `MovingFunds`,\\n /// or `Terminated` state.\\n function notifyWalletMovedFundsSweepTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n WalletState walletState = wallet.state;\\n\\n require(\\n walletState == WalletState.Live ||\\n walletState == WalletState.MovingFunds ||\\n walletState == WalletState.Terminated,\\n \\\"Wallet must be in Live or MovingFunds or Terminated state\\\"\\n );\\n\\n if (\\n walletState == Wallets.WalletState.Live ||\\n walletState == Wallets.WalletState.MovingFunds\\n ) {\\n self.ecdsaWalletRegistry.seize(\\n self.movedFundsSweepTimeoutSlashingAmount,\\n self.movedFundsSweepTimeoutNotifierRewardMultiplier,\\n msg.sender,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n\\n terminateWallet(self, walletPubKeyHash);\\n }\\n }\\n\\n /// @notice Called when a wallet which was challenged for a fraud did not\\n /// defeat the challenge before the timeout. Slashes and terminates\\n /// the wallet who failed to defeat the challenge. If the wallet is\\n /// already terminated, it does nothing.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet which was\\n /// supposed to sweep funds.\\n /// @param walletMembersIDs Identifiers of the wallet signing group members.\\n /// @param challenger Address of the party which submitted the fraud\\n /// challenge.\\n /// @dev Requirements:\\n /// - The wallet must be in the `Live`, `MovingFunds`, `Closing`\\n /// or `Terminated` state.\\n function notifyWalletFraudChallengeDefeatTimeout(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n uint32[] calldata walletMembersIDs,\\n address challenger\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n WalletState walletState = wallet.state;\\n\\n if (\\n walletState == Wallets.WalletState.Live ||\\n walletState == Wallets.WalletState.MovingFunds ||\\n walletState == Wallets.WalletState.Closing\\n ) {\\n self.ecdsaWalletRegistry.seize(\\n self.fraudSlashingAmount,\\n self.fraudNotifierRewardMultiplier,\\n challenger,\\n wallet.ecdsaWalletID,\\n walletMembersIDs\\n );\\n\\n terminateWallet(self, walletPubKeyHash);\\n } else if (walletState == Wallets.WalletState.Terminated) {\\n // This is a special case when the wallet was already terminated\\n // due to a previous deliberate protocol violation. In that\\n // case, this function should be still callable for other fraud\\n // challenges timeouts in order to let the challenger unlock its\\n // ETH deposit back. However, the wallet termination logic is\\n // not called and the challenger is not rewarded.\\n } else {\\n revert(\\n \\\"Wallet must be in Live or MovingFunds or Closing or Terminated state\\\"\\n );\\n }\\n }\\n\\n /// @notice Requests a wallet to move their funds. If the wallet balance\\n /// is zero, the wallet closing begins immediately. If the move\\n /// funds request refers to the current active wallet, such a wallet\\n /// is no longer considered active and the active wallet slot\\n /// is unset allowing to trigger a new wallet creation immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the Live state.\\n function moveFunds(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n if (wallet.mainUtxoHash == bytes32(0)) {\\n // If the wallet has no main UTXO, that means its BTC balance\\n // is zero and the wallet closing should begin immediately.\\n beginWalletClosing(self, walletPubKeyHash);\\n } else {\\n // Otherwise, initialize the moving funds process.\\n wallet.state = WalletState.MovingFunds;\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.movingFundsRequestedAt = uint32(block.timestamp);\\n\\n // slither-disable-next-line reentrancy-events\\n emit WalletMovingFunds(wallet.ecdsaWalletID, walletPubKeyHash);\\n }\\n\\n if (self.activeWalletPubKeyHash == walletPubKeyHash) {\\n // If the move funds request refers to the current active wallet,\\n // unset the active wallet and make the wallet creation process\\n // possible in order to get a new healthy active wallet.\\n delete self.activeWalletPubKeyHash;\\n }\\n\\n self.liveWalletsCount--;\\n }\\n\\n /// @notice Begins the closing period of the given wallet.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the\\n /// MovingFunds state.\\n function beginWalletClosing(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n // Initialize the closing period.\\n wallet.state = WalletState.Closing;\\n /* solhint-disable-next-line not-rely-on-time */\\n wallet.closingStartedAt = uint32(block.timestamp);\\n\\n // slither-disable-next-line reentrancy-events\\n emit WalletClosing(wallet.ecdsaWalletID, walletPubKeyHash);\\n }\\n\\n /// @notice Finalizes the closing period of the given wallet and notifies\\n /// the ECDSA registry about this fact.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the Closing state.\\n function finalizeWalletClosing(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n wallet.state = WalletState.Closed;\\n\\n emit WalletClosed(wallet.ecdsaWalletID, walletPubKeyHash);\\n\\n self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);\\n }\\n\\n /// @notice Terminates the given wallet and notifies the ECDSA registry\\n /// about this fact. If the wallet termination refers to the current\\n /// active wallet, such a wallet is no longer considered active and\\n /// the active wallet slot is unset allowing to trigger a new wallet\\n /// creation immediately.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @dev Requirements:\\n /// - The caller must make sure that the wallet is in the\\n /// Live or MovingFunds or Closing state.\\n function terminateWallet(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash\\n ) internal {\\n Wallet storage wallet = self.registeredWallets[walletPubKeyHash];\\n\\n if (wallet.state == WalletState.Live) {\\n self.liveWalletsCount--;\\n }\\n\\n wallet.state = WalletState.Terminated;\\n\\n // slither-disable-next-line reentrancy-events\\n emit WalletTerminated(wallet.ecdsaWalletID, walletPubKeyHash);\\n\\n if (self.activeWalletPubKeyHash == walletPubKeyHash) {\\n // If termination refers to the current active wallet,\\n // unset the active wallet and make the wallet creation process\\n // possible in order to get a new healthy active wallet.\\n delete self.activeWalletPubKeyHash;\\n }\\n\\n self.ecdsaWalletRegistry.closeWallet(wallet.ecdsaWalletID);\\n }\\n\\n /// @notice Gets BTC balance for given the wallet.\\n /// @param walletPubKeyHash 20-byte public key hash of the wallet.\\n /// @param walletMainUtxo Data of the wallet's main UTXO, as currently\\n /// known on the Ethereum chain.\\n /// @return walletBtcBalance Current BTC balance for the given wallet.\\n /// @dev Requirements:\\n /// - `walletMainUtxo` components must point to the recent main UTXO\\n /// of the given wallet, as currently known on the Ethereum chain.\\n /// If the wallet has no main UTXO, this parameter can be empty as it\\n /// is ignored.\\n function getWalletBtcBalance(\\n BridgeState.Storage storage self,\\n bytes20 walletPubKeyHash,\\n BitcoinTx.UTXO calldata walletMainUtxo\\n ) internal view returns (uint64 walletBtcBalance) {\\n bytes32 walletMainUtxoHash = self\\n .registeredWallets[walletPubKeyHash]\\n .mainUtxoHash;\\n\\n // If the wallet has a main UTXO hash set, cross-check it with the\\n // provided plain-text parameter and get the transaction output value\\n // as BTC balance. Otherwise, the BTC balance is just zero.\\n if (walletMainUtxoHash != bytes32(0)) {\\n require(\\n keccak256(\\n abi.encodePacked(\\n walletMainUtxo.txHash,\\n walletMainUtxo.txOutputIndex,\\n walletMainUtxo.txOutputValue\\n )\\n ) == walletMainUtxoHash,\\n \\\"Invalid wallet main UTXO data\\\"\\n );\\n\\n walletBtcBalance = walletMainUtxo.txOutputValue;\\n }\\n\\n return walletBtcBalance;\\n }\\n}\\n\",\"keccak256\":\"0x7d0ddff8dc20c10b1e62c8dba304c928c8d8de1c8b6c4d3a1c557dae99743435\",\"license\":\"GPL-3.0-only\"},\"contracts/token/TBTC.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@thesis/solidity-contracts/contracts/token/ERC20WithPermit.sol\\\";\\nimport \\\"@thesis/solidity-contracts/contracts/token/MisfundRecovery.sol\\\";\\n\\ncontract TBTC is ERC20WithPermit, MisfundRecovery {\\n constructor() ERC20WithPermit(\\\"tBTC v2\\\", \\\"tBTC\\\") {}\\n}\\n\",\"keccak256\":\"0x4542aaa48f7682005b815253768b6433c811daff5d2727db256d5f1879f5ccbf\",\"license\":\"GPL-3.0-only\"},\"contracts/vault/IVault.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"../bank/IReceiveBalanceApproval.sol\\\";\\n\\n/// @title Bank Vault interface\\n/// @notice `IVault` is an interface for a smart contract consuming Bank\\n/// balances of other contracts or externally owned accounts (EOA).\\ninterface IVault is IReceiveBalanceApproval {\\n /// @notice Called by the Bank in `increaseBalanceAndCall` function after\\n /// increasing the balance in the Bank for the vault. It happens in\\n /// the same transaction in which deposits were swept by the Bridge.\\n /// This allows the depositor to route their deposit revealed to the\\n /// Bridge to the particular smart contract (vault) in the same\\n /// transaction in which the deposit is revealed. This way, the\\n /// depositor does not have to execute additional transaction after\\n /// the deposit gets swept by the Bridge to approve and transfer\\n /// their balance to the vault.\\n /// @param depositors Addresses of depositors whose deposits have been swept.\\n /// @param depositedAmounts Amounts deposited by individual depositors and\\n /// swept.\\n /// @dev The implementation must ensure this function can only be called\\n /// by the Bank. The Bank guarantees that the vault's balance was\\n /// increased by the sum of all deposited amounts before this function\\n /// is called, in the same transaction.\\n function receiveBalanceIncrease(\\n address[] calldata depositors,\\n uint256[] calldata depositedAmounts\\n ) external;\\n}\\n\",\"keccak256\":\"0x12866d625abab349324ee28c6f6ec0114eaa7069ea0c5f7c7b23f6a0f833ae60\",\"license\":\"GPL-3.0-only\"},\"contracts/vault/TBTCOptimisticMinting.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"../bridge/Bridge.sol\\\";\\nimport \\\"../bridge/Deposit.sol\\\";\\nimport \\\"../GovernanceUtils.sol\\\";\\n\\n/// @title TBTC Optimistic Minting\\n/// @notice The Optimistic Minting mechanism allows to mint TBTC before\\n/// `TBTCVault` receives the Bank balance. There are two permissioned\\n/// sets in the system: Minters and Guardians, both set up in 1-of-n\\n/// mode. Minters observe the revealed deposits and request minting TBTC.\\n/// Any single Minter can perform this action. There is an\\n/// `optimisticMintingDelay` between the time of the request from\\n/// a Minter to the time TBTC is minted. During the time of the delay,\\n/// any Guardian can cancel the minting.\\n/// @dev This functionality is a part of `TBTCVault`. It is implemented in\\n/// a separate abstract contract to achieve better separation of concerns\\n/// and easier-to-follow code.\\nabstract contract TBTCOptimisticMinting is Ownable {\\n // Represents optimistic minting request for the given deposit revealed\\n // to the Bridge.\\n struct OptimisticMintingRequest {\\n // UNIX timestamp at which the optimistic minting was requested.\\n uint64 requestedAt;\\n // UNIX timestamp at which the optimistic minting was finalized.\\n // 0 if not yet finalized.\\n uint64 finalizedAt;\\n }\\n\\n /// @notice The time delay that needs to pass between initializing and\\n /// finalizing the upgrade of governable parameters.\\n uint256 public constant GOVERNANCE_DELAY = 24 hours;\\n\\n /// @notice Multiplier to convert satoshi to TBTC token units.\\n uint256 public constant SATOSHI_MULTIPLIER = 10**10;\\n\\n Bridge public immutable bridge;\\n\\n /// @notice Indicates if the optimistic minting has been paused. Only the\\n /// Governance can pause optimistic minting. Note that the pause of\\n /// the optimistic minting does not stop the standard minting flow\\n /// where wallets sweep deposits.\\n bool public isOptimisticMintingPaused;\\n\\n /// @notice Divisor used to compute the treasury fee taken from each\\n /// optimistically minted deposit and transferred to the treasury\\n /// upon finalization of the optimistic mint. This fee is computed\\n /// as follows: `fee = amount / optimisticMintingFeeDivisor`.\\n /// For example, if the fee needs to be 2%, the\\n /// `optimisticMintingFeeDivisor` should be set to `50` because\\n /// `1/50 = 0.02 = 2%`.\\n /// The optimistic minting fee does not replace the deposit treasury\\n /// fee cut by the Bridge. The optimistic fee is a percentage AFTER\\n /// the treasury fee is cut:\\n /// `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`\\n uint32 public optimisticMintingFeeDivisor = 500; // 1/500 = 0.002 = 0.2%\\n\\n /// @notice The time that needs to pass between the moment the optimistic\\n /// minting is requested and the moment optimistic minting is\\n /// finalized with minting TBTC.\\n uint32 public optimisticMintingDelay = 3 hours;\\n\\n /// @notice Indicates if the given address is a Minter. Only Minters can\\n /// request optimistic minting.\\n mapping(address => bool) public isMinter;\\n\\n /// @notice List of all Minters.\\n /// @dev May be used to establish an order in which the Minters should\\n /// request for an optimistic minting.\\n address[] public minters;\\n\\n /// @notice Indicates if the given address is a Guardian. Only Guardians can\\n /// cancel requested optimistic minting.\\n mapping(address => bool) public isGuardian;\\n\\n /// @notice Collection of all revealed deposits for which the optimistic\\n /// minting was requested. Indexed by a deposit key computed as\\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\\n mapping(uint256 => OptimisticMintingRequest)\\n public optimisticMintingRequests;\\n\\n /// @notice Optimistic minting debt value per depositor's address. The debt\\n /// represents the total value of all depositor's deposits revealed\\n /// to the Bridge that has not been yet swept and led to the\\n /// optimistic minting of TBTC. When `TBTCVault` sweeps a deposit,\\n /// the debt is fully or partially paid off, no matter if that\\n /// particular swept deposit was used for the optimistic minting or\\n /// not. The values are in 1e18 Ethereum precision.\\n mapping(address => uint256) public optimisticMintingDebt;\\n\\n /// @notice New optimistic minting fee divisor value. Set only when the\\n /// parameter update process is pending. Once the update gets\\n // finalized, this will be the value of the divisor.\\n uint32 public newOptimisticMintingFeeDivisor;\\n /// @notice The timestamp at which the update of the optimistic minting fee\\n /// divisor started. Zero if update is not in progress.\\n uint256 public optimisticMintingFeeUpdateInitiatedTimestamp;\\n\\n /// @notice New optimistic minting delay value. Set only when the parameter\\n /// update process is pending. Once the update gets finalized, this\\n // will be the value of the delay.\\n uint32 public newOptimisticMintingDelay;\\n /// @notice The timestamp at which the update of the optimistic minting\\n /// delay started. Zero if update is not in progress.\\n uint256 public optimisticMintingDelayUpdateInitiatedTimestamp;\\n\\n event OptimisticMintingRequested(\\n address indexed minter,\\n uint256 indexed depositKey,\\n address indexed depositor,\\n uint256 amount, // amount in 1e18 Ethereum precision\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n );\\n event OptimisticMintingFinalized(\\n address indexed minter,\\n uint256 indexed depositKey,\\n address indexed depositor,\\n uint256 optimisticMintingDebt\\n );\\n event OptimisticMintingCancelled(\\n address indexed guardian,\\n uint256 indexed depositKey\\n );\\n event OptimisticMintingDebtRepaid(\\n address indexed depositor,\\n uint256 optimisticMintingDebt\\n );\\n event MinterAdded(address indexed minter);\\n event MinterRemoved(address indexed minter);\\n event GuardianAdded(address indexed guardian);\\n event GuardianRemoved(address indexed guardian);\\n event OptimisticMintingPaused();\\n event OptimisticMintingUnpaused();\\n\\n event OptimisticMintingFeeUpdateStarted(\\n uint32 newOptimisticMintingFeeDivisor\\n );\\n event OptimisticMintingFeeUpdated(uint32 newOptimisticMintingFeeDivisor);\\n\\n event OptimisticMintingDelayUpdateStarted(uint32 newOptimisticMintingDelay);\\n event OptimisticMintingDelayUpdated(uint32 newOptimisticMintingDelay);\\n\\n modifier onlyMinter() {\\n require(isMinter[msg.sender], \\\"Caller is not a minter\\\");\\n _;\\n }\\n\\n modifier onlyGuardian() {\\n require(isGuardian[msg.sender], \\\"Caller is not a guardian\\\");\\n _;\\n }\\n\\n modifier onlyOwnerOrGuardian() {\\n require(\\n owner() == msg.sender || isGuardian[msg.sender],\\n \\\"Caller is not the owner or guardian\\\"\\n );\\n _;\\n }\\n\\n modifier whenOptimisticMintingNotPaused() {\\n require(!isOptimisticMintingPaused, \\\"Optimistic minting paused\\\");\\n _;\\n }\\n\\n modifier onlyAfterGovernanceDelay(uint256 updateInitiatedTimestamp) {\\n GovernanceUtils.onlyAfterGovernanceDelay(\\n updateInitiatedTimestamp,\\n GOVERNANCE_DELAY\\n );\\n _;\\n }\\n\\n constructor(Bridge _bridge) {\\n require(\\n address(_bridge) != address(0),\\n \\\"Bridge can not be the zero address\\\"\\n );\\n\\n bridge = _bridge;\\n }\\n\\n /// @dev Mints the given amount of TBTC to the given depositor's address.\\n /// Implemented by TBTCVault.\\n function _mint(address minter, uint256 amount) internal virtual;\\n\\n /// @notice Allows to fetch a list of all Minters.\\n function getMinters() external view returns (address[] memory) {\\n return minters;\\n }\\n\\n /// @notice Allows a Minter to request for an optimistic minting of TBTC.\\n /// The following conditions must be met:\\n /// - There is no optimistic minting request for the deposit,\\n /// finalized or not.\\n /// - The deposit with the given Bitcoin funding transaction hash\\n /// and output index has been revealed to the Bridge.\\n /// - The deposit has not been swept yet.\\n /// - The deposit is targeted into the TBTCVault.\\n /// - The optimistic minting is not paused.\\n /// After calling this function, the Minter has to wait for\\n /// `optimisticMintingDelay` before finalizing the mint with a call\\n /// to finalizeOptimisticMint.\\n /// @dev The deposit done on the Bitcoin side must be revealed early enough\\n /// to the Bridge on Ethereum to pass the Bridge's validation. The\\n /// validation passes successfully only if the deposit reveal is done\\n /// respectively earlier than the moment when the deposit refund\\n /// locktime is reached, i.e. the deposit becomes refundable. It may\\n /// happen that the wallet does not sweep a revealed deposit and one of\\n /// the Minters requests an optimistic mint for that deposit just\\n /// before the locktime is reached. Guardians must cancel optimistic\\n /// minting for this deposit because the wallet will not be able to\\n /// sweep it. The on-chain optimistic minting code does not perform any\\n /// validation for gas efficiency: it would have to perform the same\\n /// validation as `validateDepositRefundLocktime` and expect the entire\\n /// `DepositRevealInfo` to be passed to assemble the expected script\\n /// hash on-chain. Guardians must validate if the deposit happened on\\n /// Bitcoin, that the script hash has the expected format, and that the\\n /// wallet is an active one so they can also validate the time left for\\n /// the refund.\\n function requestOptimisticMint(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) external onlyMinter whenOptimisticMintingNotPaused {\\n uint256 depositKey = calculateDepositKey(\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n\\n OptimisticMintingRequest storage request = optimisticMintingRequests[\\n depositKey\\n ];\\n require(\\n request.requestedAt == 0,\\n \\\"Optimistic minting already requested for the deposit\\\"\\n );\\n\\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\\n\\n require(deposit.revealedAt != 0, \\\"The deposit has not been revealed\\\");\\n require(deposit.sweptAt == 0, \\\"The deposit is already swept\\\");\\n require(deposit.vault == address(this), \\\"Unexpected vault address\\\");\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n request.requestedAt = uint64(block.timestamp);\\n\\n emit OptimisticMintingRequested(\\n msg.sender,\\n depositKey,\\n deposit.depositor,\\n deposit.amount * SATOSHI_MULTIPLIER,\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n }\\n\\n /// @notice Allows a Minter to finalize previously requested optimistic\\n /// minting. The following conditions must be met:\\n /// - The optimistic minting has been requested for the given\\n /// deposit.\\n /// - The deposit has not been swept yet.\\n /// - At least `optimisticMintingDelay` passed since the optimistic\\n /// minting was requested for the given deposit.\\n /// - The optimistic minting has not been finalized earlier for the\\n /// given deposit.\\n /// - The optimistic minting request for the given deposit has not\\n /// been canceled by a Guardian.\\n /// - The optimistic minting is not paused.\\n /// This function mints TBTC and increases `optimisticMintingDebt`\\n /// for the given depositor. The optimistic minting request is\\n /// marked as finalized.\\n function finalizeOptimisticMint(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) external onlyMinter whenOptimisticMintingNotPaused {\\n uint256 depositKey = calculateDepositKey(\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n\\n OptimisticMintingRequest storage request = optimisticMintingRequests[\\n depositKey\\n ];\\n require(\\n request.requestedAt != 0,\\n \\\"Optimistic minting not requested for the deposit\\\"\\n );\\n require(\\n request.finalizedAt == 0,\\n \\\"Optimistic minting already finalized for the deposit\\\"\\n );\\n\\n require(\\n /* solhint-disable-next-line not-rely-on-time */\\n block.timestamp > request.requestedAt + optimisticMintingDelay,\\n \\\"Optimistic minting delay has not passed yet\\\"\\n );\\n\\n Deposit.DepositRequest memory deposit = bridge.deposits(depositKey);\\n require(deposit.sweptAt == 0, \\\"The deposit is already swept\\\");\\n\\n // Bridge, when sweeping, cuts a deposit treasury fee and splits\\n // Bitcoin miner fee for the sweep transaction evenly between the\\n // depositors in the sweep.\\n //\\n // When tokens are optimistically minted, we do not know what the\\n // Bitcoin miner fee for the sweep transaction will look like.\\n // The Bitcoin miner fee is ignored. When sweeping, the miner fee is\\n // subtracted so the optimisticMintingDebt may stay non-zero after the\\n // deposit is swept.\\n //\\n // This imbalance is supposed to be solved by a donation to the Bridge.\\n uint256 amountToMint = (deposit.amount - deposit.treasuryFee) *\\n SATOSHI_MULTIPLIER;\\n\\n // The Optimistic Minting mechanism may additionally cut a fee from the\\n // amount that is left after deducting the Bridge deposit treasury fee.\\n // Think of this fee as an extra payment for faster processing of\\n // deposits. One does not need to use the Optimistic Minting mechanism\\n // and they may wait for the Bridge to sweep their deposit if they do\\n // not want to pay the Optimistic Minting fee.\\n uint256 optimisticMintFee = optimisticMintingFeeDivisor > 0\\n ? (amountToMint / optimisticMintingFeeDivisor)\\n : 0;\\n\\n // Both the optimistic minting fee and the share that goes to the\\n // depositor are optimistically minted. All TBTC that is optimistically\\n // minted should be added to the optimistic minting debt. When the\\n // deposit is swept, it is paying off both the depositor's share and the\\n // treasury's share (optimistic minting fee).\\n uint256 newDebt = optimisticMintingDebt[deposit.depositor] +\\n amountToMint;\\n optimisticMintingDebt[deposit.depositor] = newDebt;\\n\\n _mint(deposit.depositor, amountToMint - optimisticMintFee);\\n if (optimisticMintFee > 0) {\\n _mint(bridge.treasury(), optimisticMintFee);\\n }\\n\\n /* solhint-disable-next-line not-rely-on-time */\\n request.finalizedAt = uint64(block.timestamp);\\n\\n emit OptimisticMintingFinalized(\\n msg.sender,\\n depositKey,\\n deposit.depositor,\\n newDebt\\n );\\n }\\n\\n /// @notice Allows a Guardian to cancel optimistic minting request. The\\n /// following conditions must be met:\\n /// - The optimistic minting request for the given deposit exists.\\n /// - The optimistic minting request for the given deposit has not\\n /// been finalized yet.\\n /// Optimistic minting request is removed. It is possible to request\\n /// optimistic minting again for the same deposit later.\\n /// @dev Guardians must validate the following conditions for every deposit\\n /// for which the optimistic minting was requested:\\n /// - The deposit happened on Bitcoin side and it has enough\\n /// confirmations.\\n /// - The optimistic minting has been requested early enough so that\\n /// the wallet has enough time to sweep the deposit.\\n /// - The wallet is an active one and it does perform sweeps or it will\\n /// perform sweeps once the sweeps are activated.\\n function cancelOptimisticMint(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) external onlyGuardian {\\n uint256 depositKey = calculateDepositKey(\\n fundingTxHash,\\n fundingOutputIndex\\n );\\n\\n OptimisticMintingRequest storage request = optimisticMintingRequests[\\n depositKey\\n ];\\n require(\\n request.requestedAt != 0,\\n \\\"Optimistic minting not requested for the deposit\\\"\\n );\\n require(\\n request.finalizedAt == 0,\\n \\\"Optimistic minting already finalized for the deposit\\\"\\n );\\n\\n // Delete it. It allows to request optimistic minting for the given\\n // deposit again. Useful in case of an errant Guardian.\\n delete optimisticMintingRequests[depositKey];\\n\\n emit OptimisticMintingCancelled(msg.sender, depositKey);\\n }\\n\\n /// @notice Adds the address to the Minter list.\\n function addMinter(address minter) external onlyOwner {\\n require(!isMinter[minter], \\\"This address is already a minter\\\");\\n isMinter[minter] = true;\\n minters.push(minter);\\n emit MinterAdded(minter);\\n }\\n\\n /// @notice Removes the address from the Minter list.\\n function removeMinter(address minter) external onlyOwnerOrGuardian {\\n require(isMinter[minter], \\\"This address is not a minter\\\");\\n delete isMinter[minter];\\n\\n // We do not expect too many Minters so a simple loop is safe.\\n for (uint256 i = 0; i < minters.length; i++) {\\n if (minters[i] == minter) {\\n minters[i] = minters[minters.length - 1];\\n // slither-disable-next-line costly-loop\\n minters.pop();\\n break;\\n }\\n }\\n\\n emit MinterRemoved(minter);\\n }\\n\\n /// @notice Adds the address to the Guardian set.\\n function addGuardian(address guardian) external onlyOwner {\\n require(!isGuardian[guardian], \\\"This address is already a guardian\\\");\\n isGuardian[guardian] = true;\\n emit GuardianAdded(guardian);\\n }\\n\\n /// @notice Removes the address from the Guardian set.\\n function removeGuardian(address guardian) external onlyOwner {\\n require(isGuardian[guardian], \\\"This address is not a guardian\\\");\\n delete isGuardian[guardian];\\n emit GuardianRemoved(guardian);\\n }\\n\\n /// @notice Pauses the optimistic minting. Note that the pause of the\\n /// optimistic minting does not stop the standard minting flow\\n /// where wallets sweep deposits.\\n function pauseOptimisticMinting() external onlyOwner {\\n require(\\n !isOptimisticMintingPaused,\\n \\\"Optimistic minting already paused\\\"\\n );\\n isOptimisticMintingPaused = true;\\n emit OptimisticMintingPaused();\\n }\\n\\n /// @notice Unpauses the optimistic minting.\\n function unpauseOptimisticMinting() external onlyOwner {\\n require(isOptimisticMintingPaused, \\\"Optimistic minting is not paused\\\");\\n isOptimisticMintingPaused = false;\\n emit OptimisticMintingUnpaused();\\n }\\n\\n /// @notice Begins the process of updating optimistic minting fee.\\n /// The fee is computed as follows:\\n /// `fee = amount / optimisticMintingFeeDivisor`.\\n /// For example, if the fee needs to be 2% of each deposit,\\n /// the `optimisticMintingFeeDivisor` should be set to `50` because\\n /// `1/50 = 0.02 = 2%`.\\n /// @dev See the documentation for optimisticMintingFeeDivisor.\\n function beginOptimisticMintingFeeUpdate(\\n uint32 _newOptimisticMintingFeeDivisor\\n ) external onlyOwner {\\n /* solhint-disable-next-line not-rely-on-time */\\n optimisticMintingFeeUpdateInitiatedTimestamp = block.timestamp;\\n newOptimisticMintingFeeDivisor = _newOptimisticMintingFeeDivisor;\\n emit OptimisticMintingFeeUpdateStarted(_newOptimisticMintingFeeDivisor);\\n }\\n\\n /// @notice Finalizes the update process of the optimistic minting fee.\\n function finalizeOptimisticMintingFeeUpdate()\\n external\\n onlyOwner\\n onlyAfterGovernanceDelay(optimisticMintingFeeUpdateInitiatedTimestamp)\\n {\\n optimisticMintingFeeDivisor = newOptimisticMintingFeeDivisor;\\n emit OptimisticMintingFeeUpdated(newOptimisticMintingFeeDivisor);\\n\\n newOptimisticMintingFeeDivisor = 0;\\n optimisticMintingFeeUpdateInitiatedTimestamp = 0;\\n }\\n\\n /// @notice Begins the process of updating optimistic minting delay.\\n function beginOptimisticMintingDelayUpdate(\\n uint32 _newOptimisticMintingDelay\\n ) external onlyOwner {\\n /* solhint-disable-next-line not-rely-on-time */\\n optimisticMintingDelayUpdateInitiatedTimestamp = block.timestamp;\\n newOptimisticMintingDelay = _newOptimisticMintingDelay;\\n emit OptimisticMintingDelayUpdateStarted(_newOptimisticMintingDelay);\\n }\\n\\n /// @notice Finalizes the update process of the optimistic minting delay.\\n function finalizeOptimisticMintingDelayUpdate()\\n external\\n onlyOwner\\n onlyAfterGovernanceDelay(optimisticMintingDelayUpdateInitiatedTimestamp)\\n {\\n optimisticMintingDelay = newOptimisticMintingDelay;\\n emit OptimisticMintingDelayUpdated(newOptimisticMintingDelay);\\n\\n newOptimisticMintingDelay = 0;\\n optimisticMintingDelayUpdateInitiatedTimestamp = 0;\\n }\\n\\n /// @notice Calculates deposit key the same way as the Bridge contract.\\n /// The deposit key is computed as\\n /// `keccak256(fundingTxHash | fundingOutputIndex)`.\\n function calculateDepositKey(\\n bytes32 fundingTxHash,\\n uint32 fundingOutputIndex\\n ) public pure returns (uint256) {\\n return\\n uint256(\\n keccak256(abi.encodePacked(fundingTxHash, fundingOutputIndex))\\n );\\n }\\n\\n /// @notice Used by `TBTCVault.receiveBalanceIncrease` to repay the optimistic\\n /// minting debt before TBTC is minted. When optimistic minting is\\n /// finalized, debt equal to the value of the deposit being\\n /// a subject of the optimistic minting is incurred. When `TBTCVault`\\n /// sweeps a deposit, the debt is fully or partially paid off, no\\n /// matter if that particular deposit was used for the optimistic\\n /// minting or not.\\n /// @dev See `TBTCVault.receiveBalanceIncrease`\\n /// @param depositor The depositor whose balance increase is received.\\n /// @param amount The balance increase amount for the depositor received.\\n /// @return The TBTC amount that should be minted after paying off the\\n /// optimistic minting debt.\\n function repayOptimisticMintingDebt(address depositor, uint256 amount)\\n internal\\n returns (uint256)\\n {\\n uint256 debt = optimisticMintingDebt[depositor];\\n if (debt == 0) {\\n return amount;\\n }\\n\\n if (amount > debt) {\\n optimisticMintingDebt[depositor] = 0;\\n emit OptimisticMintingDebtRepaid(depositor, 0);\\n return amount - debt;\\n } else {\\n optimisticMintingDebt[depositor] = debt - amount;\\n emit OptimisticMintingDebtRepaid(depositor, debt - amount);\\n return 0;\\n }\\n }\\n}\\n\",\"keccak256\":\"0xa4bfc703dafeca2acb0e3c5c96d7f052692147b3812496f426f43e25ef4ba123\",\"license\":\"GPL-3.0-only\"},\"contracts/vault/TBTCVault.sol\":{\"content\":\"// SPDX-License-Identifier: GPL-3.0-only\\n\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588 \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\u2588\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n// \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c \\u2590\\u2588\\u2588\\u2588\\u2588\\u258c\\n\\npragma solidity 0.8.17;\\n\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\n\\nimport \\\"./IVault.sol\\\";\\nimport \\\"./TBTCOptimisticMinting.sol\\\";\\nimport \\\"../bank/Bank.sol\\\";\\nimport \\\"../token/TBTC.sol\\\";\\n\\n/// @title TBTC application vault\\n/// @notice TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of\\n/// Bitcoin. It facilitates Bitcoin holders to act on the Ethereum\\n/// blockchain and access the decentralized finance (DeFi) ecosystem.\\n/// TBTC Vault mints and unmints TBTC based on Bitcoin balances in the\\n/// Bank.\\n/// @dev TBTC Vault is the owner of TBTC token contract and is the only contract\\n/// minting the token.\\ncontract TBTCVault is IVault, Ownable, TBTCOptimisticMinting {\\n using SafeERC20 for IERC20;\\n\\n Bank public immutable bank;\\n TBTC public immutable tbtcToken;\\n\\n /// @notice The address of a new TBTC vault. Set only when the upgrade\\n /// process is pending. Once the upgrade gets finalized, the new\\n /// TBTC vault will become an owner of TBTC token.\\n address public newVault;\\n /// @notice The timestamp at which an upgrade to a new TBTC vault was\\n /// initiated. Set only when the upgrade process is pending.\\n uint256 public upgradeInitiatedTimestamp;\\n\\n event Minted(address indexed to, uint256 amount);\\n event Unminted(address indexed from, uint256 amount);\\n\\n event UpgradeInitiated(address newVault, uint256 timestamp);\\n event UpgradeFinalized(address newVault);\\n\\n modifier onlyBank() {\\n require(msg.sender == address(bank), \\\"Caller is not the Bank\\\");\\n _;\\n }\\n\\n constructor(\\n Bank _bank,\\n TBTC _tbtcToken,\\n Bridge _bridge\\n ) TBTCOptimisticMinting(_bridge) {\\n require(\\n address(_bank) != address(0),\\n \\\"Bank can not be the zero address\\\"\\n );\\n\\n require(\\n address(_tbtcToken) != address(0),\\n \\\"TBTC token can not be the zero address\\\"\\n );\\n\\n bank = _bank;\\n tbtcToken = _tbtcToken;\\n }\\n\\n /// @notice Mints the given `amount` of TBTC to the caller previously\\n /// transferring `amount / SATOSHI_MULTIPLIER` of the Bank balance\\n /// from caller to TBTC Vault. If `amount` is not divisible by\\n /// SATOSHI_MULTIPLIER, the remainder is left on the caller's\\n /// Bank balance.\\n /// @dev TBTC Vault must have an allowance for caller's balance in the\\n /// Bank for at least `amount / SATOSHI_MULTIPLIER`.\\n /// @param amount Amount of TBTC to mint.\\n function mint(uint256 amount) external {\\n (uint256 convertibleAmount, , uint256 satoshis) = amountToSatoshis(\\n amount\\n );\\n\\n require(\\n bank.balanceOf(msg.sender) >= satoshis,\\n \\\"Amount exceeds balance in the bank\\\"\\n );\\n _mint(msg.sender, convertibleAmount);\\n bank.transferBalanceFrom(msg.sender, address(this), satoshis);\\n }\\n\\n /// @notice Transfers `satoshis` of the Bank balance from the caller\\n /// to TBTC Vault and mints `satoshis * SATOSHI_MULTIPLIER` of TBTC\\n /// to the caller.\\n /// @dev Can only be called by the Bank via `approveBalanceAndCall`.\\n /// @param owner The owner who approved their Bank balance.\\n /// @param satoshis Amount of satoshis used to mint TBTC.\\n function receiveBalanceApproval(\\n address owner,\\n uint256 satoshis,\\n bytes calldata\\n ) external override onlyBank {\\n require(\\n bank.balanceOf(owner) >= satoshis,\\n \\\"Amount exceeds balance in the bank\\\"\\n );\\n _mint(owner, satoshis * SATOSHI_MULTIPLIER);\\n bank.transferBalanceFrom(owner, address(this), satoshis);\\n }\\n\\n /// @notice Mints the same amount of TBTC as the deposited satoshis amount\\n /// multiplied by SATOSHI_MULTIPLIER for each depositor in the array.\\n /// Can only be called by the Bank after the Bridge swept deposits\\n /// and Bank increased balance for the vault.\\n /// @dev Fails if `depositors` array is empty. Expects the length of\\n /// `depositors` and `depositedSatoshiAmounts` is the same.\\n function receiveBalanceIncrease(\\n address[] calldata depositors,\\n uint256[] calldata depositedSatoshiAmounts\\n ) external override onlyBank {\\n require(depositors.length != 0, \\\"No depositors specified\\\");\\n for (uint256 i = 0; i < depositors.length; i++) {\\n address depositor = depositors[i];\\n uint256 satoshis = depositedSatoshiAmounts[i];\\n _mint(\\n depositor,\\n repayOptimisticMintingDebt(\\n depositor,\\n satoshis * SATOSHI_MULTIPLIER\\n )\\n );\\n }\\n }\\n\\n /// @notice Burns `amount` of TBTC from the caller's balance and transfers\\n /// `amount / SATOSHI_MULTIPLIER` back to the caller's balance in\\n /// the Bank. If `amount` is not divisible by SATOSHI_MULTIPLIER,\\n /// the remainder is left on the caller's account.\\n /// @dev Caller must have at least `amount` of TBTC approved to\\n /// TBTC Vault.\\n /// @param amount Amount of TBTC to unmint.\\n function unmint(uint256 amount) external {\\n (uint256 convertibleAmount, , ) = amountToSatoshis(amount);\\n\\n _unmint(msg.sender, convertibleAmount);\\n }\\n\\n /// @notice Burns `amount` of TBTC from the caller's balance and transfers\\n /// `amount / SATOSHI_MULTIPLIER` of Bank balance to the Bridge\\n /// requesting redemption based on the provided `redemptionData`.\\n /// If `amount` is not divisible by SATOSHI_MULTIPLIER, the\\n /// remainder is left on the caller's account.\\n /// @dev Caller must have at least `amount` of TBTC approved to\\n /// TBTC Vault.\\n /// @param amount Amount of TBTC to unmint and request to redeem in Bridge.\\n /// @param redemptionData Redemption data in a format expected from\\n /// `redemptionData` parameter of Bridge's `receiveBalanceApproval`\\n /// function.\\n function unmintAndRedeem(uint256 amount, bytes calldata redemptionData)\\n external\\n {\\n (uint256 convertibleAmount, , ) = amountToSatoshis(amount);\\n\\n _unmintAndRedeem(msg.sender, convertibleAmount, redemptionData);\\n }\\n\\n /// @notice Burns `amount` of TBTC from the caller's balance. If `extraData`\\n /// is empty, transfers `amount` back to the caller's balance in the\\n /// Bank. If `extraData` is not empty, requests redemption in the\\n /// Bridge using the `extraData` as a `redemptionData` parameter to\\n /// Bridge's `receiveBalanceApproval` function.\\n /// If `amount` is not divisible by SATOSHI_MULTIPLIER, the\\n /// remainder is left on the caller's account. Note that it may\\n /// left a token approval equal to the remainder.\\n /// @dev This function is doing the same as `unmint` or `unmintAndRedeem`\\n /// (depending on `extraData` parameter) but it allows to execute\\n /// unminting without a separate approval transaction. The function can\\n /// be called only via `approveAndCall` of TBTC token.\\n /// @param from TBTC token holder executing unminting.\\n /// @param amount Amount of TBTC to unmint.\\n /// @param token TBTC token address.\\n /// @param extraData Redemption data in a format expected from\\n /// `redemptionData` parameter of Bridge's `receiveBalanceApproval`\\n /// function. If empty, `receiveApproval` is not requesting a\\n /// redemption of Bank balance but is instead performing just TBTC\\n /// unminting to a Bank balance.\\n function receiveApproval(\\n address from,\\n uint256 amount,\\n address token,\\n bytes calldata extraData\\n ) external {\\n require(token == address(tbtcToken), \\\"Token is not TBTC\\\");\\n require(msg.sender == token, \\\"Only TBTC caller allowed\\\");\\n (uint256 convertibleAmount, , ) = amountToSatoshis(amount);\\n if (extraData.length == 0) {\\n _unmint(from, convertibleAmount);\\n } else {\\n _unmintAndRedeem(from, convertibleAmount, extraData);\\n }\\n }\\n\\n /// @notice Initiates vault upgrade process. The upgrade process needs to be\\n /// finalized with a call to `finalizeUpgrade` function after the\\n /// `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can\\n /// initiate the upgrade.\\n /// @param _newVault The new vault address.\\n function initiateUpgrade(address _newVault) external onlyOwner {\\n require(_newVault != address(0), \\\"New vault address cannot be zero\\\");\\n /* solhint-disable-next-line not-rely-on-time */\\n emit UpgradeInitiated(_newVault, block.timestamp);\\n /* solhint-disable-next-line not-rely-on-time */\\n upgradeInitiatedTimestamp = block.timestamp;\\n newVault = _newVault;\\n }\\n\\n /// @notice Allows the governance to finalize vault upgrade process. The\\n /// upgrade process needs to be first initiated with a call to\\n /// `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass.\\n /// Once the upgrade is finalized, the new vault becomes the owner\\n /// of the TBTC token and receives the whole Bank balance of this\\n /// vault.\\n function finalizeUpgrade()\\n external\\n onlyOwner\\n onlyAfterGovernanceDelay(upgradeInitiatedTimestamp)\\n {\\n emit UpgradeFinalized(newVault);\\n // slither-disable-next-line reentrancy-no-eth\\n tbtcToken.transferOwnership(newVault);\\n bank.transferBalance(newVault, bank.balanceOf(address(this)));\\n newVault = address(0);\\n upgradeInitiatedTimestamp = 0;\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC20\\n /// token sent mistakenly to the TBTC token contract address.\\n /// @param token Address of the recovered ERC20 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param amount Recovered amount.\\n function recoverERC20FromToken(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n tbtcToken.recoverERC20(token, recipient, amount);\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC721\\n /// token sent mistakenly to the TBTC token contract address.\\n /// @param token Address of the recovered ERC721 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param tokenId Identifier of the recovered token.\\n /// @param data Additional data.\\n function recoverERC721FromToken(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n tbtcToken.recoverERC721(token, recipient, tokenId, data);\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC20\\n /// token sent - mistakenly or not - to the vault address. This\\n /// function should be used to withdraw TBTC v1 tokens transferred\\n /// to TBTCVault as a result of VendingMachine > TBTCVault upgrade.\\n /// @param token Address of the recovered ERC20 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param amount Recovered amount.\\n function recoverERC20(\\n IERC20 token,\\n address recipient,\\n uint256 amount\\n ) external onlyOwner {\\n token.safeTransfer(recipient, amount);\\n }\\n\\n /// @notice Allows the governance of the TBTCVault to recover any ERC721\\n /// token sent mistakenly to the vault address.\\n /// @param token Address of the recovered ERC721 token contract.\\n /// @param recipient Address the recovered token should be sent to.\\n /// @param tokenId Identifier of the recovered token.\\n /// @param data Additional data.\\n function recoverERC721(\\n IERC721 token,\\n address recipient,\\n uint256 tokenId,\\n bytes calldata data\\n ) external onlyOwner {\\n token.safeTransferFrom(address(this), recipient, tokenId, data);\\n }\\n\\n /// @notice Returns the amount of TBTC to be minted/unminted, the remainder,\\n /// and the Bank balance to be transferred for the given mint/unmint.\\n /// Note that if the `amount` is not divisible by SATOSHI_MULTIPLIER,\\n /// the remainder is left on the caller's account when minting or\\n /// unminting.\\n /// @return convertibleAmount Amount of TBTC to be minted/unminted.\\n /// @return remainder Not convertible remainder if amount is not divisible\\n /// by SATOSHI_MULTIPLIER.\\n /// @return satoshis Amount in satoshis - the Bank balance to be transferred\\n /// for the given mint/unmint\\n function amountToSatoshis(uint256 amount)\\n public\\n view\\n returns (\\n uint256 convertibleAmount,\\n uint256 remainder,\\n uint256 satoshis\\n )\\n {\\n remainder = amount % SATOSHI_MULTIPLIER;\\n convertibleAmount = amount - remainder;\\n satoshis = convertibleAmount / SATOSHI_MULTIPLIER;\\n }\\n\\n // slither-disable-next-line calls-loop\\n function _mint(address minter, uint256 amount) internal override {\\n emit Minted(minter, amount);\\n tbtcToken.mint(minter, amount);\\n }\\n\\n /// @dev `amount` MUST be divisible by SATOSHI_MULTIPLIER with no change.\\n function _unmint(address unminter, uint256 amount) internal {\\n emit Unminted(unminter, amount);\\n tbtcToken.burnFrom(unminter, amount);\\n bank.transferBalance(unminter, amount / SATOSHI_MULTIPLIER);\\n }\\n\\n /// @dev `amount` MUST be divisible by SATOSHI_MULTIPLIER with no change.\\n function _unmintAndRedeem(\\n address redeemer,\\n uint256 amount,\\n bytes calldata redemptionData\\n ) internal {\\n emit Unminted(redeemer, amount);\\n tbtcToken.burnFrom(redeemer, amount);\\n bank.approveBalanceAndCall(\\n address(bridge),\\n amount / SATOSHI_MULTIPLIER,\\n redemptionData\\n );\\n }\\n}\\n\",\"keccak256\":\"0x98f5c27ce01926f53eb30daa11bd9b2b0cf24274d81a99a04a74ed220f0a69cb\",\"license\":\"GPL-3.0-only\"}},\"version\":1}", + "bytecode": "0x60e060405260008054600160a81b600160e81b031916650a8c0000007d60aa1b1790553480156200002f57600080fd5b5060405162003e2f38038062003e2f83398101604081905262000052916200020c565b806200005e33620001a3565b6001600160a01b038116620000c55760405162461bcd60e51b815260206004820152602260248201527f4272696467652063616e206e6f7420626520746865207a65726f206164647265604482015261737360f01b60648201526084015b60405180910390fd5b6001600160a01b039081166080528316620001235760405162461bcd60e51b815260206004820181905260248201527f42616e6b2063616e206e6f7420626520746865207a65726f20616464726573736044820152606401620000bc565b6001600160a01b0382166200018a5760405162461bcd60e51b815260206004820152602660248201527f5442544320746f6b656e2063616e206e6f7420626520746865207a65726f206160448201526564647265737360d01b6064820152608401620000bc565b506001600160a01b0391821660a0521660c05262000260565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b03811681146200020957600080fd5b50565b6000806000606084860312156200022257600080fd5b83516200022f81620001f3565b60208501519093506200024281620001f3565b60408501519092506200025581620001f3565b809150509250925092565b60805160a05160c051613b136200031c600039600081816107330152818161098001528181610cf401528181611fb0015281816123b101528181612d6d01528181612ed8015261301f01526000818161057901528181610d6101528181610ebc01528181610f4a0152818161104f0152818161242d015281816125650152818161265601528181612f36015261307d01526000818161075a015281816115720152818161170e01528181611d620152612f650152613b136000f3fe608060405234801561001057600080fd5b50600436106103365760003560e01c80638532c511116101b2578063a410d29b116100f9578063c36b32b7116100a2578063e5d3d7141161007c578063e5d3d7141461072e578063e78cea9214610755578063f2fde38b1461077c578063fc4e51f61461078f57600080fd5b8063c36b32b7146106ff578063c7ba034714610712578063d0ff65b51461071e57600080fd5b8063b0b54895116100d3578063b0b54895146106cd578063b9f78798146106d7578063bc7b6b99146106f757600080fd5b8063a410d29b1461068f578063a526d83b14610697578063aa271e1a146106aa57600080fd5b8063951315261161015b5780639a508c8e116101355780639a508c8e1461066b578063a0712d6814610673578063a0cceb951461068657600080fd5b80639513152614610647578063983b2d56146106505780639a4e36d51461066357600080fd5b8063897f712c1161018c578063897f712c1461060f5780638da5cb5b146106235780638f4ffcb11461063457600080fd5b80638532c511146105d95780638623ec7b146105e957806388aaf0c8146105fc57600080fd5b806347c1ffdb116102815780636c626aa41161022a5780637445a5a0116102045780637445a5a01461054657806376cdb03b1461057457806380df5ed2146105b3578063820b5513146105c657600080fd5b80636c626aa4146104d0578063714041561461052b578063715018a61461053e57600080fd5b806364e779b11161025b57806364e779b1146104955780636abe3a6c146104a85780636b32810b146104bb57600080fd5b806347c1ffdb1461046357806353dce4df1461046b5780635ae2da461461047e57600080fd5b80633092afd5116102e3578063461c6373116102bd578063461c63731461042a578063475d05701461043d578063479aa9271461045057600080fd5b80633092afd5146103fb578063317dfa761461040e57806341906ab71461042157600080fd5b80631171bda9116103145780631171bda9146103b4578063124f65bd146103c75780632e73e398146103e857600080fd5b806309b53f511461033b5780630c68ba211461036c5780630f3425731461039f575b600080fd5b60005461035290600160a81b900463ffffffff1681565b60405163ffffffff90911681526020015b60405180910390f35b61038f61037a3660046133e9565b60036020526000908152604090205460ff1681565b6040519015158152602001610363565b6103b26103ad366004613418565b6107a2565b005b6103b26103c2366004613435565b610842565b6103da6103d5366004613476565b6108a3565b604051908152602001610363565b6103b26103f63660046134ef565b610908565b6103b26104093660046133e9565b6109f6565b6103b261041c366004613435565b610c65565b6103da60095481565b6103b26104383660046135a7565b610d56565b6103b261044b366004613613565b610eb1565b6103b261045e3660046133e9565b6110b2565b6103b26111b7565b6103b2610479366004613663565b611295565b60005461035290600160c81b900463ffffffff1681565b6103b26104a33660046136af565b6112b6565b6103b26104b6366004613476565b6112d3565b6104c3611820565b60405161036391906136c8565b61050a6104de3660046136af565b60046020526000908152604090205467ffffffffffffffff808216916801000000000000000090041682565b6040805167ffffffffffffffff938416815292909116602083015201610363565b6103b26105393660046133e9565b611882565b6103b261197b565b6105596105543660046136af565b6119cf565b60408051938452602084019290925290820152606001610363565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610363565b6103b26105c1366004613476565b611a07565b6103b26105d4366004613476565b611bf3565b6006546103529063ffffffff1681565b61059b6105f73660046136af565b611f84565b600a5461059b906001600160a01b031681565b60005461038f90600160a01b900460ff1681565b6000546001600160a01b031661059b565b6103b2610642366004613715565b611fae565b6103da600b5481565b6103b261065e3660046133e9565b6120c3565b6103b2612200565b6103b26122de565b6103b26106813660046136af565b612535565b6103da60075481565b6103b261268d565b6103b26106a53660046133e9565b612775565b61038f6106b83660046133e9565b60016020526000908152604090205460ff1681565b6103da6201518081565b6103da6106e53660046133e9565b60056020526000908152604090205481565b6103b2612898565b6103b261070d366004613418565b61296f565b6103da6402540be40081565b6008546103529063ffffffff1681565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6103b261078a3660046133e9565b612a03565b6103b261079d3660046134ef565b612ad3565b6000546001600160a01b031633146107ef5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064015b60405180910390fd5b426009556008805463ffffffff191663ffffffff83169081179091556040519081527f682bc0fb7e0d6bcb974cf556b95f68533cafc411d83d9f33ac192ccf45dda605906020015b60405180910390a150565b6000546001600160a01b0316331461088a5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b61089e6001600160a01b0384168383612b68565b505050565b600082826040516020016108e692919091825260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016602082015260240190565b6040516020818303038152906040528051906020012060001c90505b92915050565b6000546001600160a01b031633146109505760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517ffc4e51f60000000000000000000000000000000000000000000000000000000081526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063fc4e51f6906109bd9088908890889088908890600401613794565b600060405180830381600087803b1580156109d757600080fd5b505af11580156109eb573d6000803e3d6000fd5b505050505050505050565b33610a096000546001600160a01b031690565b6001600160a01b03161480610a2d57503360009081526003602052604090205460ff165b610a9f5760405162461bcd60e51b815260206004820152602360248201527f43616c6c6572206973206e6f7420746865206f776e6572206f7220677561726460448201527f69616e000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b03811660009081526001602052604090205460ff16610b075760405162461bcd60e51b815260206004820152601c60248201527f546869732061646472657373206973206e6f742061206d696e7465720000000060448201526064016107e6565b6001600160a01b0381166000908152600160205260408120805460ff191690555b600254811015610c2d57816001600160a01b031660028281548110610b4f57610b4f6137c7565b6000918252602090912001546001600160a01b031603610c1b5760028054610b79906001906137f3565b81548110610b8957610b896137c7565b600091825260209091200154600280546001600160a01b039092169183908110610bb557610bb56137c7565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506002805480610bf457610bf4613806565b600082815260209020810160001990810180546001600160a01b0319169055019055610c2d565b80610c258161381c565b915050610b28565b506040516001600160a01b038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000546001600160a01b03163314610cad5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517f1171bda90000000000000000000000000000000000000000000000000000000081526001600160a01b0384811660048301528381166024830152604482018390527f00000000000000000000000000000000000000000000000000000000000000001690631171bda9906064015b600060405180830381600087803b158015610d3957600080fd5b505af1158015610d4d573d6000803e3d6000fd5b50505050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610dce5760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6000839003610e1f5760405162461bcd60e51b815260206004820152601760248201527f4e6f206465706f7369746f72732073706563696669656400000000000000000060448201526064016107e6565b60005b83811015610eaa576000858583818110610e3e57610e3e6137c7565b9050602002016020810190610e5391906133e9565b90506000848484818110610e6957610e696137c7565b905060200201359050610e9582610e90846402540be40085610e8b9190613835565b612be8565b612ceb565b50508080610ea29061381c565b915050610e22565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f295760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6040516370a0823160e01b81526001600160a01b03858116600483015284917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa158015610f93573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb7919061384c565b10156110105760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b61102384610e906402540be40086613835565b604051631f1b6d2760e21b81526001600160a01b038581166004830152306024830152604482018590527f00000000000000000000000000000000000000000000000000000000000000001690637c6db49c906064015b600060405180830381600087803b15801561109457600080fd5b505af11580156110a8573d6000803e3d6000fd5b5050505050505050565b6000546001600160a01b031633146110fa5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b0381166111505760405162461bcd60e51b815260206004820181905260248201527f4e6577207661756c7420616464726573732063616e6e6f74206265207a65726f60448201526064016107e6565b604080516001600160a01b03831681524260208201527f5cc842cab066489e13292128663547c68705dbf476f0131e0107f155719c6124910160405180910390a142600b55600a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146111ff5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b60075461120f8162015180612dc6565b600654600080547fffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160a81b81029290921790556040519081527fa7f4ce7c3586e2000cdec6b25c5e7d0b20f9b4f435aa22d9c1feb32dbb506f779060200160405180910390a1506006805463ffffffff191690556000600755565b60006112a0846119cf565b505090506112b033828585612e6f565b50505050565b60006112c1826119cf565b505090506112cf3382612fb6565b5050565b3360009081526001602052604090205460ff166113325760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff161561138c5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b600061139883836108a3565b600081815260046020526040812080549293509167ffffffffffffffff16900361142a5760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff16156114b55760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60005481546114db91600160c81b900463ffffffff169067ffffffffffffffff16613865565b67ffffffffffffffff1642116115595760405162461bcd60e51b815260206004820152602b60248201527f4f7074696d6973746963206d696e74696e672064656c617920686173206e6f7460448201527f207061737365642079657400000000000000000000000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa1580156115c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115e591906138b9565b90508060a0015163ffffffff166000146116415760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60006402540be4008260800151836020015161165d9190613966565b67ffffffffffffffff166116719190613835565b6000805491925090600160a81b900463ffffffff166116915760006116ab565b6000546116ab90600160a81b900463ffffffff168361399d565b83516001600160a01b0316600090815260056020526040812054919250906116d49084906139b1565b84516001600160a01b03166000908152600560205260409020819055845190915061170390610e9084866137f3565b8115611794576117947f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166361d027b36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561176a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061178e91906139c4565b83612ceb565b84547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16680100000000000000004267ffffffffffffffff160217855583516040518281526001600160a01b0390911690879033907f2cffebf26d639426e79514d100febae8b2c63e700e5dc0fa6c88a129633506369060200160405180910390a45050505050505050565b6060600280548060200260200160405190810160405280929190818152602001828054801561187857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161185a575b5050505050905090565b6000546001600160a01b031633146118ca5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff166119325760405162461bcd60e51b815260206004820152601e60248201527f546869732061646472657373206973206e6f74206120677561726469616e000060448201526064016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19169055517fb8107d0c6b40be480ce3172ee66ba6d64b71f6b1685a851340036e6e2e3e3c529190a250565b6000546001600160a01b031633146119c35760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6119cd6000613105565b565b600080806119e26402540be400856139e1565b91506119ee82856137f3565b92506119ff6402540be4008461399d565b929491935050565b3360009081526003602052604090205460ff16611a665760405162461bcd60e51b815260206004820152601860248201527f43616c6c6572206973206e6f74206120677561726469616e000000000000000060448201526064016107e6565b6000611a7283836108a3565b600081815260046020526040812080549293509167ffffffffffffffff169003611b045760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff1615611b8f5760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60008281526004602052604080822080547fffffffffffffffffffffffffffffffff0000000000000000000000000000000016905551839133917f1256b41d4b18d922811c358ab80cb0375aae28f45373de35cfda580662193fcd9190a350505050565b3360009081526001602052604090205460ff16611c525760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff1615611cac5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b6000611cb883836108a3565b600081815260046020526040902080549192509067ffffffffffffffff1615611d495760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c726561647920726571756560448201527f7374656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa158015611db1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dd591906138b9565b9050806040015163ffffffff16600003611e3b5760405162461bcd60e51b815260206004820152602160248201527f546865206465706f73697420686173206e6f74206265656e2072657665616c656044820152601960fa1b60648201526084016107e6565b60a081015163ffffffff1615611e935760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60608101516001600160a01b03163014611eef5760405162461bcd60e51b815260206004820152601860248201527f556e6578706563746564207661756c742061646472657373000000000000000060448201526064016107e6565b815467ffffffffffffffff19164267ffffffffffffffff908116919091178355815160208301516001600160a01b0390911691859133917f36f39c606d55d7dd2a05b8c4e41e9a6ca8c501cea10009c1762f6826a146e05591611f59916402540be4009116613835565b60408051918252602082018b905263ffffffff8a169082015260600160405180910390a45050505050565b60028181548110611f9457600080fd5b6000918252602090912001546001600160a01b0316905081565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161461202f5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e206973206e6f74205442544300000000000000000000000000000060448201526064016107e6565b336001600160a01b038416146120875760405162461bcd60e51b815260206004820152601860248201527f4f6e6c7920544254432063616c6c657220616c6c6f776564000000000000000060448201526064016107e6565b6000612092856119cf565b509091505060008290036120af576120aa8682612fb6565b6120bb565b6120bb86828585612e6f565b505050505050565b6000546001600160a01b0316331461210b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526001602052604090205460ff16156121745760405162461bcd60e51b815260206004820181905260248201527f54686973206164647265737320697320616c72656164792061206d696e74657260448201526064016107e6565b6001600160a01b0381166000818152600160208190526040808320805460ff19168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180546001600160a01b03191684179055517f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f69190a250565b6000546001600160a01b031633146122485760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6009546122588162015180612dc6565b600854600080547fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160c81b81029290921790556040519081527ff52de3377e3ae270d1e38f99b9b8d587814643811516ea55ba4d597f9950d4ec9060200160405180910390a1506008805463ffffffff191690556000600955565b6000546001600160a01b031633146123265760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600b546123368162015180612dc6565b600a546040516001600160a01b0390911681527f81a9bb8030ed4116b405800280e065110a37afb57b69948e714c97fab23475ec9060200160405180910390a1600a546040517ff2fde38b0000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201527f00000000000000000000000000000000000000000000000000000000000000009091169063f2fde38b90602401600060405180830381600087803b1580156123f757600080fd5b505af115801561240b573d6000803e3d6000fd5b5050600a546040516370a0823160e01b81523060048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811694506356a6d9ef93509091169083906370a0823190602401602060405180830381865afa158015612483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124a7919061384c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561250557600080fd5b505af1158015612519573d6000803e3d6000fd5b5050600a80546001600160a01b031916905550506000600b5550565b600080612541836119cf565b6040516370a0823160e01b8152336004820152929450925082916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691506370a0823190602401602060405180830381865afa1580156125ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d1919061384c565b101561262a5760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b6126343383612ceb565b604051631f1b6d2760e21b8152336004820152306024820152604481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690637c6db49c90606401610d1f565b6000546001600160a01b031633146126d55760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff16156127395760405162461bcd60e51b815260206004820152602160248201527f4f7074696d6973746963206d696e74696e6720616c72656164792070617573656044820152601960fa1b60648201526084016107e6565b6000805460ff60a01b1916600160a01b1781556040517f23a83c8aeda8c831401c17b5bfb8b2ead79fcfe9c027fe34a4f8576e2c7c74cc9190a1565b6000546001600160a01b031633146127bd5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff161561284c5760405162461bcd60e51b815260206004820152602260248201527f54686973206164647265737320697320616c726561647920612067756172646960448201527f616e00000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19166001179055517f038596bb31e2e7d3d9f184d4c98b310103f6d7f5830e5eec32bffe6f1728f9699190a250565b6000546001600160a01b031633146128e05760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff166129395760405162461bcd60e51b815260206004820181905260248201527f4f7074696d6973746963206d696e74696e67206973206e6f742070617573656460448201526064016107e6565b6000805460ff60a01b191681556040517fcb27470ed9568d9eeb8939707bafc19404d908a26ce5f468a6aa781024fd6a839190a1565b6000546001600160a01b031633146129b75760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b426007556006805463ffffffff191663ffffffff83169081179091556040519081527f0dbfec7f12acffbb5cec595ac4370907eaf84caa7025dd71f4021433be79eba990602001610837565b6000546001600160a01b03163314612a4b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b038116612ac75760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016107e6565b612ad081613105565b50565b6000546001600160a01b03163314612b1b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde906109bd9030908890889088908890600401613794565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261089e908490613155565b6001600160a01b038216600090815260056020526040812054808203612c115782915050610902565b80831115612c78576001600160a01b0384166000818152600560209081526040808320839055519182527fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8910160405180910390a2612c7081846137f3565b915050610902565b612c8283826137f3565b6001600160a01b0385166000818152600560205260409020919091557fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8612cc985846137f3565b60405190815260200160405180910390a26000915050610902565b5092915050565b816001600160a01b03167f30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe82604051612d2691815260200190565b60405180910390a26040517f40c10f190000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906340c10f19906044015b600060405180830381600087803b158015612db257600080fd5b505af11580156120bb573d6000803e3d6000fd5b60008211612e165760405162461bcd60e51b815260206004820152601460248201527f4368616e6765206e6f7420696e6974696174656400000000000000000000000060448201526064016107e6565b80612e2183426137f3565b10156112cf5760405162461bcd60e51b815260206004820181905260248201527f476f7665726e616e63652064656c617920686173206e6f7420656c617073656460448201526064016107e6565b836001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b884604051612eaa91815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038581166004830152602482018590527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b158015612f1c57600080fd5b505af1158015612f30573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634a38757e7f00000000000000000000000000000000000000000000000000000000000000006402540be40086612f95919061399d565b85856040518563ffffffff1660e01b815260040161107a94939291906139f5565b816001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b882604051612ff191815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b15801561306357600080fd5b505af1158015613077573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356a6d9ef836402540be400846130bc919061399d565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401612d98565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006131aa826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661323a9092919063ffffffff16565b80519091501561089e57808060200190518101906131c89190613a28565b61089e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016107e6565b60606132498484600085613253565b90505b9392505050565b6060824710156132cb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b0385163b6133225760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107e6565b600080866001600160a01b0316858760405161333e9190613a6e565b60006040518083038185875af1925050503d806000811461337b576040519150601f19603f3d011682016040523d82523d6000602084013e613380565b606091505b509150915061339082828661339b565b979650505050505050565b606083156133aa57508161324c565b8251156133ba5782518084602001fd5b8160405162461bcd60e51b81526004016107e69190613a8a565b6001600160a01b0381168114612ad057600080fd5b6000602082840312156133fb57600080fd5b813561324c816133d4565b63ffffffff81168114612ad057600080fd5b60006020828403121561342a57600080fd5b813561324c81613406565b60008060006060848603121561344a57600080fd5b8335613455816133d4565b92506020840135613465816133d4565b929592945050506040919091013590565b6000806040838503121561348957600080fd5b82359150602083013561349b81613406565b809150509250929050565b60008083601f8401126134b857600080fd5b50813567ffffffffffffffff8111156134d057600080fd5b6020830191508360208285010111156134e857600080fd5b9250929050565b60008060008060006080868803121561350757600080fd5b8535613512816133d4565b94506020860135613522816133d4565b935060408601359250606086013567ffffffffffffffff81111561354557600080fd5b613551888289016134a6565b969995985093965092949392505050565b60008083601f84011261357457600080fd5b50813567ffffffffffffffff81111561358c57600080fd5b6020830191508360208260051b85010111156134e857600080fd5b600080600080604085870312156135bd57600080fd5b843567ffffffffffffffff808211156135d557600080fd5b6135e188838901613562565b909650945060208701359150808211156135fa57600080fd5b5061360787828801613562565b95989497509550505050565b6000806000806060858703121561362957600080fd5b8435613634816133d4565b935060208501359250604085013567ffffffffffffffff81111561365757600080fd5b613607878288016134a6565b60008060006040848603121561367857600080fd5b83359250602084013567ffffffffffffffff81111561369657600080fd5b6136a2868287016134a6565b9497909650939450505050565b6000602082840312156136c157600080fd5b5035919050565b6020808252825182820181905260009190848201906040850190845b818110156137095783516001600160a01b0316835292840192918401916001016136e4565b50909695505050505050565b60008060008060006080868803121561372d57600080fd5b8535613738816133d4565b945060208601359350604086013561374f816133d4565b9250606086013567ffffffffffffffff81111561354557600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60006001600160a01b0380881683528087166020840152508460408301526080606083015261339060808301848661376b565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115610902576109026137dd565b634e487b7160e01b600052603160045260246000fd5b60006001820161382e5761382e6137dd565b5060010190565b8082028115828204841417610902576109026137dd565b60006020828403121561385e57600080fd5b5051919050565b67ffffffffffffffff818116838216019080821115612ce457612ce46137dd565b8051613891816133d4565b919050565b805167ffffffffffffffff8116811461389157600080fd5b805161389181613406565b600060c082840312156138cb57600080fd5b60405160c0810181811067ffffffffffffffff821117156138fc57634e487b7160e01b600052604160045260246000fd5b60405261390883613886565b815261391660208401613896565b6020820152613927604084016138ae565b604082015261393860608401613886565b606082015261394960808401613896565b608082015261395a60a084016138ae565b60a08201529392505050565b67ffffffffffffffff828116828216039080821115612ce457612ce46137dd565b634e487b7160e01b600052601260045260246000fd5b6000826139ac576139ac613987565b500490565b80820180821115610902576109026137dd565b6000602082840312156139d657600080fd5b815161324c816133d4565b6000826139f0576139f0613987565b500690565b6001600160a01b0385168152836020820152606060408201526000613a1e60608301848661376b565b9695505050505050565b600060208284031215613a3a57600080fd5b8151801515811461324c57600080fd5b60005b83811015613a65578181015183820152602001613a4d565b50506000910152565b60008251613a80818460208701613a4a565b9190910192915050565b6020815260008251806020840152613aa9816040850160208701613a4a565b601f01601f1916919091016040019291505056fe4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572a26469706673582212209a07381069555c36987780153c945020363fa37b34dc75e1273fdc2b8ab2e0d464736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106103365760003560e01c80638532c511116101b2578063a410d29b116100f9578063c36b32b7116100a2578063e5d3d7141161007c578063e5d3d7141461072e578063e78cea9214610755578063f2fde38b1461077c578063fc4e51f61461078f57600080fd5b8063c36b32b7146106ff578063c7ba034714610712578063d0ff65b51461071e57600080fd5b8063b0b54895116100d3578063b0b54895146106cd578063b9f78798146106d7578063bc7b6b99146106f757600080fd5b8063a410d29b1461068f578063a526d83b14610697578063aa271e1a146106aa57600080fd5b8063951315261161015b5780639a508c8e116101355780639a508c8e1461066b578063a0712d6814610673578063a0cceb951461068657600080fd5b80639513152614610647578063983b2d56146106505780639a4e36d51461066357600080fd5b8063897f712c1161018c578063897f712c1461060f5780638da5cb5b146106235780638f4ffcb11461063457600080fd5b80638532c511146105d95780638623ec7b146105e957806388aaf0c8146105fc57600080fd5b806347c1ffdb116102815780636c626aa41161022a5780637445a5a0116102045780637445a5a01461054657806376cdb03b1461057457806380df5ed2146105b3578063820b5513146105c657600080fd5b80636c626aa4146104d0578063714041561461052b578063715018a61461053e57600080fd5b806364e779b11161025b57806364e779b1146104955780636abe3a6c146104a85780636b32810b146104bb57600080fd5b806347c1ffdb1461046357806353dce4df1461046b5780635ae2da461461047e57600080fd5b80633092afd5116102e3578063461c6373116102bd578063461c63731461042a578063475d05701461043d578063479aa9271461045057600080fd5b80633092afd5146103fb578063317dfa761461040e57806341906ab71461042157600080fd5b80631171bda9116103145780631171bda9146103b4578063124f65bd146103c75780632e73e398146103e857600080fd5b806309b53f511461033b5780630c68ba211461036c5780630f3425731461039f575b600080fd5b60005461035290600160a81b900463ffffffff1681565b60405163ffffffff90911681526020015b60405180910390f35b61038f61037a3660046133e9565b60036020526000908152604090205460ff1681565b6040519015158152602001610363565b6103b26103ad366004613418565b6107a2565b005b6103b26103c2366004613435565b610842565b6103da6103d5366004613476565b6108a3565b604051908152602001610363565b6103b26103f63660046134ef565b610908565b6103b26104093660046133e9565b6109f6565b6103b261041c366004613435565b610c65565b6103da60095481565b6103b26104383660046135a7565b610d56565b6103b261044b366004613613565b610eb1565b6103b261045e3660046133e9565b6110b2565b6103b26111b7565b6103b2610479366004613663565b611295565b60005461035290600160c81b900463ffffffff1681565b6103b26104a33660046136af565b6112b6565b6103b26104b6366004613476565b6112d3565b6104c3611820565b60405161036391906136c8565b61050a6104de3660046136af565b60046020526000908152604090205467ffffffffffffffff808216916801000000000000000090041682565b6040805167ffffffffffffffff938416815292909116602083015201610363565b6103b26105393660046133e9565b611882565b6103b261197b565b6105596105543660046136af565b6119cf565b60408051938452602084019290925290820152606001610363565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b039091168152602001610363565b6103b26105c1366004613476565b611a07565b6103b26105d4366004613476565b611bf3565b6006546103529063ffffffff1681565b61059b6105f73660046136af565b611f84565b600a5461059b906001600160a01b031681565b60005461038f90600160a01b900460ff1681565b6000546001600160a01b031661059b565b6103b2610642366004613715565b611fae565b6103da600b5481565b6103b261065e3660046133e9565b6120c3565b6103b2612200565b6103b26122de565b6103b26106813660046136af565b612535565b6103da60075481565b6103b261268d565b6103b26106a53660046133e9565b612775565b61038f6106b83660046133e9565b60016020526000908152604090205460ff1681565b6103da6201518081565b6103da6106e53660046133e9565b60056020526000908152604090205481565b6103b2612898565b6103b261070d366004613418565b61296f565b6103da6402540be40081565b6008546103529063ffffffff1681565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b61059b7f000000000000000000000000000000000000000000000000000000000000000081565b6103b261078a3660046133e9565b612a03565b6103b261079d3660046134ef565b612ad3565b6000546001600160a01b031633146107ef5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064015b60405180910390fd5b426009556008805463ffffffff191663ffffffff83169081179091556040519081527f682bc0fb7e0d6bcb974cf556b95f68533cafc411d83d9f33ac192ccf45dda605906020015b60405180910390a150565b6000546001600160a01b0316331461088a5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b61089e6001600160a01b0384168383612b68565b505050565b600082826040516020016108e692919091825260e01b7fffffffff0000000000000000000000000000000000000000000000000000000016602082015260240190565b6040516020818303038152906040528051906020012060001c90505b92915050565b6000546001600160a01b031633146109505760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517ffc4e51f60000000000000000000000000000000000000000000000000000000081526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063fc4e51f6906109bd9088908890889088908890600401613794565b600060405180830381600087803b1580156109d757600080fd5b505af11580156109eb573d6000803e3d6000fd5b505050505050505050565b33610a096000546001600160a01b031690565b6001600160a01b03161480610a2d57503360009081526003602052604090205460ff165b610a9f5760405162461bcd60e51b815260206004820152602360248201527f43616c6c6572206973206e6f7420746865206f776e6572206f7220677561726460448201527f69616e000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b03811660009081526001602052604090205460ff16610b075760405162461bcd60e51b815260206004820152601c60248201527f546869732061646472657373206973206e6f742061206d696e7465720000000060448201526064016107e6565b6001600160a01b0381166000908152600160205260408120805460ff191690555b600254811015610c2d57816001600160a01b031660028281548110610b4f57610b4f6137c7565b6000918252602090912001546001600160a01b031603610c1b5760028054610b79906001906137f3565b81548110610b8957610b896137c7565b600091825260209091200154600280546001600160a01b039092169183908110610bb557610bb56137c7565b9060005260206000200160006101000a8154816001600160a01b0302191690836001600160a01b031602179055506002805480610bf457610bf4613806565b600082815260209020810160001990810180546001600160a01b0319169055019055610c2d565b80610c258161381c565b915050610b28565b506040516001600160a01b038216907fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb6669290600090a250565b6000546001600160a01b03163314610cad5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517f1171bda90000000000000000000000000000000000000000000000000000000081526001600160a01b0384811660048301528381166024830152604482018390527f00000000000000000000000000000000000000000000000000000000000000001690631171bda9906064015b600060405180830381600087803b158015610d3957600080fd5b505af1158015610d4d573d6000803e3d6000fd5b50505050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610dce5760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6000839003610e1f5760405162461bcd60e51b815260206004820152601760248201527f4e6f206465706f7369746f72732073706563696669656400000000000000000060448201526064016107e6565b60005b83811015610eaa576000858583818110610e3e57610e3e6137c7565b9050602002016020810190610e5391906133e9565b90506000848484818110610e6957610e696137c7565b905060200201359050610e9582610e90846402540be40085610e8b9190613835565b612be8565b612ceb565b50508080610ea29061381c565b915050610e22565b5050505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f295760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f74207468652042616e6b0000000000000000000060448201526064016107e6565b6040516370a0823160e01b81526001600160a01b03858116600483015284917f0000000000000000000000000000000000000000000000000000000000000000909116906370a0823190602401602060405180830381865afa158015610f93573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fb7919061384c565b10156110105760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b61102384610e906402540be40086613835565b604051631f1b6d2760e21b81526001600160a01b038581166004830152306024830152604482018590527f00000000000000000000000000000000000000000000000000000000000000001690637c6db49c906064015b600060405180830381600087803b15801561109457600080fd5b505af11580156110a8573d6000803e3d6000fd5b5050505050505050565b6000546001600160a01b031633146110fa5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b0381166111505760405162461bcd60e51b815260206004820181905260248201527f4e6577207661756c7420616464726573732063616e6e6f74206265207a65726f60448201526064016107e6565b604080516001600160a01b03831681524260208201527f5cc842cab066489e13292128663547c68705dbf476f0131e0107f155719c6124910160405180910390a142600b55600a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000546001600160a01b031633146111ff5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b60075461120f8162015180612dc6565b600654600080547fffffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160a81b81029290921790556040519081527fa7f4ce7c3586e2000cdec6b25c5e7d0b20f9b4f435aa22d9c1feb32dbb506f779060200160405180910390a1506006805463ffffffff191690556000600755565b60006112a0846119cf565b505090506112b033828585612e6f565b50505050565b60006112c1826119cf565b505090506112cf3382612fb6565b5050565b3360009081526001602052604090205460ff166113325760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff161561138c5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b600061139883836108a3565b600081815260046020526040812080549293509167ffffffffffffffff16900361142a5760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff16156114b55760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60005481546114db91600160c81b900463ffffffff169067ffffffffffffffff16613865565b67ffffffffffffffff1642116115595760405162461bcd60e51b815260206004820152602b60248201527f4f7074696d6973746963206d696e74696e672064656c617920686173206e6f7460448201527f207061737365642079657400000000000000000000000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa1580156115c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115e591906138b9565b90508060a0015163ffffffff166000146116415760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60006402540be4008260800151836020015161165d9190613966565b67ffffffffffffffff166116719190613835565b6000805491925090600160a81b900463ffffffff166116915760006116ab565b6000546116ab90600160a81b900463ffffffff168361399d565b83516001600160a01b0316600090815260056020526040812054919250906116d49084906139b1565b84516001600160a01b03166000908152600560205260409020819055845190915061170390610e9084866137f3565b8115611794576117947f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166361d027b36040518163ffffffff1660e01b8152600401602060405180830381865afa15801561176a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061178e91906139c4565b83612ceb565b84547fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff16680100000000000000004267ffffffffffffffff160217855583516040518281526001600160a01b0390911690879033907f2cffebf26d639426e79514d100febae8b2c63e700e5dc0fa6c88a129633506369060200160405180910390a45050505050505050565b6060600280548060200260200160405190810160405280929190818152602001828054801561187857602002820191906000526020600020905b81546001600160a01b0316815260019091019060200180831161185a575b5050505050905090565b6000546001600160a01b031633146118ca5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff166119325760405162461bcd60e51b815260206004820152601e60248201527f546869732061646472657373206973206e6f74206120677561726469616e000060448201526064016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19169055517fb8107d0c6b40be480ce3172ee66ba6d64b71f6b1685a851340036e6e2e3e3c529190a250565b6000546001600160a01b031633146119c35760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6119cd6000613105565b565b600080806119e26402540be400856139e1565b91506119ee82856137f3565b92506119ff6402540be4008461399d565b929491935050565b3360009081526003602052604090205460ff16611a665760405162461bcd60e51b815260206004820152601860248201527f43616c6c6572206973206e6f74206120677561726469616e000000000000000060448201526064016107e6565b6000611a7283836108a3565b600081815260046020526040812080549293509167ffffffffffffffff169003611b045760405162461bcd60e51b815260206004820152603060248201527f4f7074696d6973746963206d696e74696e67206e6f742072657175657374656460448201527f20666f7220746865206465706f7369740000000000000000000000000000000060648201526084016107e6565b805468010000000000000000900467ffffffffffffffff1615611b8f5760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c72656164792066696e616c60448201527f697a656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b60008281526004602052604080822080547fffffffffffffffffffffffffffffffff0000000000000000000000000000000016905551839133917f1256b41d4b18d922811c358ab80cb0375aae28f45373de35cfda580662193fcd9190a350505050565b3360009081526001602052604090205460ff16611c525760405162461bcd60e51b815260206004820152601660248201527f43616c6c6572206973206e6f742061206d696e7465720000000000000000000060448201526064016107e6565b600054600160a01b900460ff1615611cac5760405162461bcd60e51b815260206004820152601960248201527f4f7074696d6973746963206d696e74696e67207061757365640000000000000060448201526064016107e6565b6000611cb883836108a3565b600081815260046020526040902080549192509067ffffffffffffffff1615611d495760405162461bcd60e51b815260206004820152603460248201527f4f7074696d6973746963206d696e74696e6720616c726561647920726571756560448201527f7374656420666f7220746865206465706f73697400000000000000000000000060648201526084016107e6565b604051630b02c43d60e41b8152600481018390526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b02c43d09060240160c060405180830381865afa158015611db1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dd591906138b9565b9050806040015163ffffffff16600003611e3b5760405162461bcd60e51b815260206004820152602160248201527f546865206465706f73697420686173206e6f74206265656e2072657665616c656044820152601960fa1b60648201526084016107e6565b60a081015163ffffffff1615611e935760405162461bcd60e51b815260206004820152601c60248201527f546865206465706f73697420697320616c72656164792073776570740000000060448201526064016107e6565b60608101516001600160a01b03163014611eef5760405162461bcd60e51b815260206004820152601860248201527f556e6578706563746564207661756c742061646472657373000000000000000060448201526064016107e6565b815467ffffffffffffffff19164267ffffffffffffffff908116919091178355815160208301516001600160a01b0390911691859133917f36f39c606d55d7dd2a05b8c4e41e9a6ca8c501cea10009c1762f6826a146e05591611f59916402540be4009116613835565b60408051918252602082018b905263ffffffff8a169082015260600160405180910390a45050505050565b60028181548110611f9457600080fd5b6000918252602090912001546001600160a01b0316905081565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b03161461202f5760405162461bcd60e51b815260206004820152601160248201527f546f6b656e206973206e6f74205442544300000000000000000000000000000060448201526064016107e6565b336001600160a01b038416146120875760405162461bcd60e51b815260206004820152601860248201527f4f6e6c7920544254432063616c6c657220616c6c6f776564000000000000000060448201526064016107e6565b6000612092856119cf565b509091505060008290036120af576120aa8682612fb6565b6120bb565b6120bb86828585612e6f565b505050505050565b6000546001600160a01b0316331461210b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526001602052604090205460ff16156121745760405162461bcd60e51b815260206004820181905260248201527f54686973206164647265737320697320616c72656164792061206d696e74657260448201526064016107e6565b6001600160a01b0381166000818152600160208190526040808320805460ff19168317905560028054928301815583527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace90910180546001600160a01b03191684179055517f6ae172837ea30b801fbfcdd4108aa1d5bf8ff775444fd70256b44e6bf3dfc3f69190a250565b6000546001600160a01b031633146122485760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6009546122588162015180612dc6565b600854600080547fffffff00000000ffffffffffffffffffffffffffffffffffffffffffffffffff1663ffffffff909216600160c81b81029290921790556040519081527ff52de3377e3ae270d1e38f99b9b8d587814643811516ea55ba4d597f9950d4ec9060200160405180910390a1506008805463ffffffff191690556000600955565b6000546001600160a01b031633146123265760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600b546123368162015180612dc6565b600a546040516001600160a01b0390911681527f81a9bb8030ed4116b405800280e065110a37afb57b69948e714c97fab23475ec9060200160405180910390a1600a546040517ff2fde38b0000000000000000000000000000000000000000000000000000000081526001600160a01b0391821660048201527f00000000000000000000000000000000000000000000000000000000000000009091169063f2fde38b90602401600060405180830381600087803b1580156123f757600080fd5b505af115801561240b573d6000803e3d6000fd5b5050600a546040516370a0823160e01b81523060048201526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811694506356a6d9ef93509091169083906370a0823190602401602060405180830381865afa158015612483573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124a7919061384c565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561250557600080fd5b505af1158015612519573d6000803e3d6000fd5b5050600a80546001600160a01b031916905550506000600b5550565b600080612541836119cf565b6040516370a0823160e01b8152336004820152929450925082916001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001691506370a0823190602401602060405180830381865afa1580156125ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125d1919061384c565b101561262a5760405162461bcd60e51b815260206004820152602260248201527f416d6f756e7420657863656564732062616c616e636520696e207468652062616044820152616e6b60f01b60648201526084016107e6565b6126343383612ceb565b604051631f1b6d2760e21b8152336004820152306024820152604481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690637c6db49c90606401610d1f565b6000546001600160a01b031633146126d55760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff16156127395760405162461bcd60e51b815260206004820152602160248201527f4f7074696d6973746963206d696e74696e6720616c72656164792070617573656044820152601960fa1b60648201526084016107e6565b6000805460ff60a01b1916600160a01b1781556040517f23a83c8aeda8c831401c17b5bfb8b2ead79fcfe9c027fe34a4f8576e2c7c74cc9190a1565b6000546001600160a01b031633146127bd5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b03811660009081526003602052604090205460ff161561284c5760405162461bcd60e51b815260206004820152602260248201527f54686973206164647265737320697320616c726561647920612067756172646960448201527f616e00000000000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b038116600081815260036020526040808220805460ff19166001179055517f038596bb31e2e7d3d9f184d4c98b310103f6d7f5830e5eec32bffe6f1728f9699190a250565b6000546001600160a01b031633146128e05760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b600054600160a01b900460ff166129395760405162461bcd60e51b815260206004820181905260248201527f4f7074696d6973746963206d696e74696e67206973206e6f742070617573656460448201526064016107e6565b6000805460ff60a01b191681556040517fcb27470ed9568d9eeb8939707bafc19404d908a26ce5f468a6aa781024fd6a839190a1565b6000546001600160a01b031633146129b75760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b426007556006805463ffffffff191663ffffffff83169081179091556040519081527f0dbfec7f12acffbb5cec595ac4370907eaf84caa7025dd71f4021433be79eba990602001610837565b6000546001600160a01b03163314612a4b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6001600160a01b038116612ac75760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016107e6565b612ad081613105565b50565b6000546001600160a01b03163314612b1b5760405162461bcd60e51b81526020600482018190526024820152600080516020613abe83398151915260448201526064016107e6565b6040517fb88d4fde0000000000000000000000000000000000000000000000000000000081526001600160a01b0386169063b88d4fde906109bd9030908890889088908890600401613794565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261089e908490613155565b6001600160a01b038216600090815260056020526040812054808203612c115782915050610902565b80831115612c78576001600160a01b0384166000818152600560209081526040808320839055519182527fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8910160405180910390a2612c7081846137f3565b915050610902565b612c8283826137f3565b6001600160a01b0385166000818152600560205260409020919091557fb30abd1b9e3d58cd8525b3d5c5aa002db1e43a150af5b172a8d16efcc7a141f8612cc985846137f3565b60405190815260200160405180910390a26000915050610902565b5092915050565b816001600160a01b03167f30385c845b448a36257a6a1716e6ad2e1bc2cbe333cde1e69fe849ad6511adfe82604051612d2691815260200190565b60405180910390a26040517f40c10f190000000000000000000000000000000000000000000000000000000081526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906340c10f19906044015b600060405180830381600087803b158015612db257600080fd5b505af11580156120bb573d6000803e3d6000fd5b60008211612e165760405162461bcd60e51b815260206004820152601460248201527f4368616e6765206e6f7420696e6974696174656400000000000000000000000060448201526064016107e6565b80612e2183426137f3565b10156112cf5760405162461bcd60e51b815260206004820181905260248201527f476f7665726e616e63652064656c617920686173206e6f7420656c617073656460448201526064016107e6565b836001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b884604051612eaa91815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038581166004830152602482018590527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b158015612f1c57600080fd5b505af1158015612f30573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634a38757e7f00000000000000000000000000000000000000000000000000000000000000006402540be40086612f95919061399d565b85856040518563ffffffff1660e01b815260040161107a94939291906139f5565b816001600160a01b03167f68751a4c3821398cb63d11609eca2440742ef19446f0c0261bfa8a13dd0748b882604051612ff191815260200190565b60405180910390a260405163079cc67960e41b81526001600160a01b038381166004830152602482018390527f000000000000000000000000000000000000000000000000000000000000000016906379cc679090604401600060405180830381600087803b15801561306357600080fd5b505af1158015613077573d6000803e3d6000fd5b505050507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166356a6d9ef836402540be400846130bc919061399d565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1681526001600160a01b0390921660048301526024820152604401612d98565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60006131aa826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b031661323a9092919063ffffffff16565b80519091501561089e57808060200190518101906131c89190613a28565b61089e5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016107e6565b60606132498484600085613253565b90505b9392505050565b6060824710156132cb5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016107e6565b6001600160a01b0385163b6133225760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016107e6565b600080866001600160a01b0316858760405161333e9190613a6e565b60006040518083038185875af1925050503d806000811461337b576040519150601f19603f3d011682016040523d82523d6000602084013e613380565b606091505b509150915061339082828661339b565b979650505050505050565b606083156133aa57508161324c565b8251156133ba5782518084602001fd5b8160405162461bcd60e51b81526004016107e69190613a8a565b6001600160a01b0381168114612ad057600080fd5b6000602082840312156133fb57600080fd5b813561324c816133d4565b63ffffffff81168114612ad057600080fd5b60006020828403121561342a57600080fd5b813561324c81613406565b60008060006060848603121561344a57600080fd5b8335613455816133d4565b92506020840135613465816133d4565b929592945050506040919091013590565b6000806040838503121561348957600080fd5b82359150602083013561349b81613406565b809150509250929050565b60008083601f8401126134b857600080fd5b50813567ffffffffffffffff8111156134d057600080fd5b6020830191508360208285010111156134e857600080fd5b9250929050565b60008060008060006080868803121561350757600080fd5b8535613512816133d4565b94506020860135613522816133d4565b935060408601359250606086013567ffffffffffffffff81111561354557600080fd5b613551888289016134a6565b969995985093965092949392505050565b60008083601f84011261357457600080fd5b50813567ffffffffffffffff81111561358c57600080fd5b6020830191508360208260051b85010111156134e857600080fd5b600080600080604085870312156135bd57600080fd5b843567ffffffffffffffff808211156135d557600080fd5b6135e188838901613562565b909650945060208701359150808211156135fa57600080fd5b5061360787828801613562565b95989497509550505050565b6000806000806060858703121561362957600080fd5b8435613634816133d4565b935060208501359250604085013567ffffffffffffffff81111561365757600080fd5b613607878288016134a6565b60008060006040848603121561367857600080fd5b83359250602084013567ffffffffffffffff81111561369657600080fd5b6136a2868287016134a6565b9497909650939450505050565b6000602082840312156136c157600080fd5b5035919050565b6020808252825182820181905260009190848201906040850190845b818110156137095783516001600160a01b0316835292840192918401916001016136e4565b50909695505050505050565b60008060008060006080868803121561372d57600080fd5b8535613738816133d4565b945060208601359350604086013561374f816133d4565b9250606086013567ffffffffffffffff81111561354557600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b60006001600160a01b0380881683528087166020840152508460408301526080606083015261339060808301848661376b565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b81810381811115610902576109026137dd565b634e487b7160e01b600052603160045260246000fd5b60006001820161382e5761382e6137dd565b5060010190565b8082028115828204841417610902576109026137dd565b60006020828403121561385e57600080fd5b5051919050565b67ffffffffffffffff818116838216019080821115612ce457612ce46137dd565b8051613891816133d4565b919050565b805167ffffffffffffffff8116811461389157600080fd5b805161389181613406565b600060c082840312156138cb57600080fd5b60405160c0810181811067ffffffffffffffff821117156138fc57634e487b7160e01b600052604160045260246000fd5b60405261390883613886565b815261391660208401613896565b6020820152613927604084016138ae565b604082015261393860608401613886565b606082015261394960808401613896565b608082015261395a60a084016138ae565b60a08201529392505050565b67ffffffffffffffff828116828216039080821115612ce457612ce46137dd565b634e487b7160e01b600052601260045260246000fd5b6000826139ac576139ac613987565b500490565b80820180821115610902576109026137dd565b6000602082840312156139d657600080fd5b815161324c816133d4565b6000826139f0576139f0613987565b500690565b6001600160a01b0385168152836020820152606060408201526000613a1e60608301848661376b565b9695505050505050565b600060208284031215613a3a57600080fd5b8151801515811461324c57600080fd5b60005b83811015613a65578181015183820152602001613a4d565b50506000910152565b60008251613a80818460208701613a4a565b9190910192915050565b6020815260008251806020840152613aa9816040850160208701613a4a565b601f01601f1916919091016040019291505056fe4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572a26469706673582212209a07381069555c36987780153c945020363fa37b34dc75e1273fdc2b8ab2e0d464736f6c63430008110033", + "devdoc": { + "details": "TBTC Vault is the owner of TBTC token contract and is the only contract minting the token.", + "kind": "dev", + "methods": { + "amountToSatoshis(uint256)": { + "returns": { + "convertibleAmount": "Amount of TBTC to be minted/unminted.", + "remainder": "Not convertible remainder if amount is not divisible by SATOSHI_MULTIPLIER.", + "satoshis": "Amount in satoshis - the Bank balance to be transferred for the given mint/unmint" + } + }, + "beginOptimisticMintingFeeUpdate(uint32)": { + "details": "See the documentation for optimisticMintingFeeDivisor." + }, + "cancelOptimisticMint(bytes32,uint32)": { + "details": "Guardians must validate the following conditions for every deposit for which the optimistic minting was requested: - The deposit happened on Bitcoin side and it has enough confirmations. - The optimistic minting has been requested early enough so that the wallet has enough time to sweep the deposit. - The wallet is an active one and it does perform sweeps or it will perform sweeps once the sweeps are activated." + }, + "initiateUpgrade(address)": { + "params": { + "_newVault": "The new vault address." + } + }, + "mint(uint256)": { + "details": "TBTC Vault must have an allowance for caller's balance in the Bank for at least `amount / SATOSHI_MULTIPLIER`.", + "params": { + "amount": "Amount of TBTC to mint." + } + }, + "owner()": { + "details": "Returns the address of the current owner." + }, + "receiveApproval(address,uint256,address,bytes)": { + "details": "This function is doing the same as `unmint` or `unmintAndRedeem` (depending on `extraData` parameter) but it allows to execute unminting without a separate approval transaction. The function can be called only via `approveAndCall` of TBTC token.", + "params": { + "amount": "Amount of TBTC to unmint.", + "extraData": "Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function. If empty, `receiveApproval` is not requesting a redemption of Bank balance but is instead performing just TBTC unminting to a Bank balance.", + "from": "TBTC token holder executing unminting.", + "token": "TBTC token address." + } + }, + "receiveBalanceApproval(address,uint256,bytes)": { + "details": "Can only be called by the Bank via `approveBalanceAndCall`.", + "params": { + "owner": "The owner who approved their Bank balance.", + "satoshis": "Amount of satoshis used to mint TBTC." + } + }, + "receiveBalanceIncrease(address[],uint256[])": { + "details": "Fails if `depositors` array is empty. Expects the length of `depositors` and `depositedSatoshiAmounts` is the same." + }, + "recoverERC20(address,address,uint256)": { + "params": { + "amount": "Recovered amount.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC20 token contract." + } + }, + "recoverERC20FromToken(address,address,uint256)": { + "params": { + "amount": "Recovered amount.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC20 token contract." + } + }, + "recoverERC721(address,address,uint256,bytes)": { + "params": { + "data": "Additional data.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC721 token contract.", + "tokenId": "Identifier of the recovered token." + } + }, + "recoverERC721FromToken(address,address,uint256,bytes)": { + "params": { + "data": "Additional data.", + "recipient": "Address the recovered token should be sent to.", + "token": "Address of the recovered ERC721 token contract.", + "tokenId": "Identifier of the recovered token." + } + }, + "renounceOwnership()": { + "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." + }, + "requestOptimisticMint(bytes32,uint32)": { + "details": "The deposit done on the Bitcoin side must be revealed early enough to the Bridge on Ethereum to pass the Bridge's validation. The validation passes successfully only if the deposit reveal is done respectively earlier than the moment when the deposit refund locktime is reached, i.e. the deposit becomes refundable. It may happen that the wallet does not sweep a revealed deposit and one of the Minters requests an optimistic mint for that deposit just before the locktime is reached. Guardians must cancel optimistic minting for this deposit because the wallet will not be able to sweep it. The on-chain optimistic minting code does not perform any validation for gas efficiency: it would have to perform the same validation as `validateDepositRefundLocktime` and expect the entire `DepositRevealInfo` to be passed to assemble the expected script hash on-chain. Guardians must validate if the deposit happened on Bitcoin, that the script hash has the expected format, and that the wallet is an active one so they can also validate the time left for the refund." + }, + "transferOwnership(address)": { + "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." + }, + "unmint(uint256)": { + "details": "Caller must have at least `amount` of TBTC approved to TBTC Vault.", + "params": { + "amount": "Amount of TBTC to unmint." + } + }, + "unmintAndRedeem(uint256,bytes)": { + "details": "Caller must have at least `amount` of TBTC approved to TBTC Vault.", + "params": { + "amount": "Amount of TBTC to unmint and request to redeem in Bridge.", + "redemptionData": "Redemption data in a format expected from `redemptionData` parameter of Bridge's `receiveBalanceApproval` function." + } + } + }, + "title": "TBTC application vault", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "GOVERNANCE_DELAY()": { + "notice": "The time delay that needs to pass between initializing and finalizing the upgrade of governable parameters." + }, + "SATOSHI_MULTIPLIER()": { + "notice": "Multiplier to convert satoshi to TBTC token units." + }, + "addGuardian(address)": { + "notice": "Adds the address to the Guardian set." + }, + "addMinter(address)": { + "notice": "Adds the address to the Minter list." + }, + "amountToSatoshis(uint256)": { + "notice": "Returns the amount of TBTC to be minted/unminted, the remainder, and the Bank balance to be transferred for the given mint/unmint. Note that if the `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account when minting or unminting." + }, + "beginOptimisticMintingDelayUpdate(uint32)": { + "notice": "Begins the process of updating optimistic minting delay." + }, + "beginOptimisticMintingFeeUpdate(uint32)": { + "notice": "Begins the process of updating optimistic minting fee. The fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2% of each deposit, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`." + }, + "calculateDepositKey(bytes32,uint32)": { + "notice": "Calculates deposit key the same way as the Bridge contract. The deposit key is computed as `keccak256(fundingTxHash | fundingOutputIndex)`." + }, + "cancelOptimisticMint(bytes32,uint32)": { + "notice": "Allows a Guardian to cancel optimistic minting request. The following conditions must be met: - The optimistic minting request for the given deposit exists. - The optimistic minting request for the given deposit has not been finalized yet. Optimistic minting request is removed. It is possible to request optimistic minting again for the same deposit later." + }, + "finalizeOptimisticMint(bytes32,uint32)": { + "notice": "Allows a Minter to finalize previously requested optimistic minting. The following conditions must be met: - The optimistic minting has been requested for the given deposit. - The deposit has not been swept yet. - At least `optimisticMintingDelay` passed since the optimistic minting was requested for the given deposit. - The optimistic minting has not been finalized earlier for the given deposit. - The optimistic minting request for the given deposit has not been canceled by a Guardian. - The optimistic minting is not paused. This function mints TBTC and increases `optimisticMintingDebt` for the given depositor. The optimistic minting request is marked as finalized." + }, + "finalizeOptimisticMintingDelayUpdate()": { + "notice": "Finalizes the update process of the optimistic minting delay." + }, + "finalizeOptimisticMintingFeeUpdate()": { + "notice": "Finalizes the update process of the optimistic minting fee." + }, + "finalizeUpgrade()": { + "notice": "Allows the governance to finalize vault upgrade process. The upgrade process needs to be first initiated with a call to `initiateUpgrade` and the `GOVERNANCE_DELAY` needs to pass. Once the upgrade is finalized, the new vault becomes the owner of the TBTC token and receives the whole Bank balance of this vault." + }, + "getMinters()": { + "notice": "Allows to fetch a list of all Minters." + }, + "initiateUpgrade(address)": { + "notice": "Initiates vault upgrade process. The upgrade process needs to be finalized with a call to `finalizeUpgrade` function after the `UPGRADE_GOVERNANCE_DELAY` passes. Only the governance can initiate the upgrade." + }, + "isGuardian(address)": { + "notice": "Indicates if the given address is a Guardian. Only Guardians can cancel requested optimistic minting." + }, + "isMinter(address)": { + "notice": "Indicates if the given address is a Minter. Only Minters can request optimistic minting." + }, + "isOptimisticMintingPaused()": { + "notice": "Indicates if the optimistic minting has been paused. Only the Governance can pause optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits." + }, + "mint(uint256)": { + "notice": "Mints the given `amount` of TBTC to the caller previously transferring `amount / SATOSHI_MULTIPLIER` of the Bank balance from caller to TBTC Vault. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's Bank balance." + }, + "minters(uint256)": { + "notice": "List of all Minters." + }, + "newOptimisticMintingDelay()": { + "notice": "New optimistic minting delay value. Set only when the parameter update process is pending. Once the update gets finalized, this" + }, + "newOptimisticMintingFeeDivisor()": { + "notice": "New optimistic minting fee divisor value. Set only when the parameter update process is pending. Once the update gets" + }, + "newVault()": { + "notice": "The address of a new TBTC vault. Set only when the upgrade process is pending. Once the upgrade gets finalized, the new TBTC vault will become an owner of TBTC token." + }, + "optimisticMintingDebt(address)": { + "notice": "Optimistic minting debt value per depositor's address. The debt represents the total value of all depositor's deposits revealed to the Bridge that has not been yet swept and led to the optimistic minting of TBTC. When `TBTCVault` sweeps a deposit, the debt is fully or partially paid off, no matter if that particular swept deposit was used for the optimistic minting or not. The values are in 1e18 Ethereum precision." + }, + "optimisticMintingDelay()": { + "notice": "The time that needs to pass between the moment the optimistic minting is requested and the moment optimistic minting is finalized with minting TBTC." + }, + "optimisticMintingDelayUpdateInitiatedTimestamp()": { + "notice": "The timestamp at which the update of the optimistic minting delay started. Zero if update is not in progress." + }, + "optimisticMintingFeeDivisor()": { + "notice": "Divisor used to compute the treasury fee taken from each optimistically minted deposit and transferred to the treasury upon finalization of the optimistic mint. This fee is computed as follows: `fee = amount / optimisticMintingFeeDivisor`. For example, if the fee needs to be 2%, the `optimisticMintingFeeDivisor` should be set to `50` because `1/50 = 0.02 = 2%`. The optimistic minting fee does not replace the deposit treasury fee cut by the Bridge. The optimistic fee is a percentage AFTER the treasury fee is cut: `optimisticMintingFee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor`" + }, + "optimisticMintingFeeUpdateInitiatedTimestamp()": { + "notice": "The timestamp at which the update of the optimistic minting fee divisor started. Zero if update is not in progress." + }, + "optimisticMintingRequests(uint256)": { + "notice": "Collection of all revealed deposits for which the optimistic minting was requested. Indexed by a deposit key computed as `keccak256(fundingTxHash | fundingOutputIndex)`." + }, + "pauseOptimisticMinting()": { + "notice": "Pauses the optimistic minting. Note that the pause of the optimistic minting does not stop the standard minting flow where wallets sweep deposits." + }, + "receiveApproval(address,uint256,address,bytes)": { + "notice": "Burns `amount` of TBTC from the caller's balance. If `extraData` is empty, transfers `amount` back to the caller's balance in the Bank. If `extraData` is not empty, requests redemption in the Bridge using the `extraData` as a `redemptionData` parameter to Bridge's `receiveBalanceApproval` function. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account. Note that it may left a token approval equal to the remainder." + }, + "receiveBalanceApproval(address,uint256,bytes)": { + "notice": "Transfers `satoshis` of the Bank balance from the caller to TBTC Vault and mints `satoshis * SATOSHI_MULTIPLIER` of TBTC to the caller." + }, + "receiveBalanceIncrease(address[],uint256[])": { + "notice": "Mints the same amount of TBTC as the deposited satoshis amount multiplied by SATOSHI_MULTIPLIER for each depositor in the array. Can only be called by the Bank after the Bridge swept deposits and Bank increased balance for the vault." + }, + "recoverERC20(address,address,uint256)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC20 token sent - mistakenly or not - to the vault address. This function should be used to withdraw TBTC v1 tokens transferred to TBTCVault as a result of VendingMachine > TBTCVault upgrade." + }, + "recoverERC20FromToken(address,address,uint256)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC20 token sent mistakenly to the TBTC token contract address." + }, + "recoverERC721(address,address,uint256,bytes)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the vault address." + }, + "recoverERC721FromToken(address,address,uint256,bytes)": { + "notice": "Allows the governance of the TBTCVault to recover any ERC721 token sent mistakenly to the TBTC token contract address." + }, + "removeGuardian(address)": { + "notice": "Removes the address from the Guardian set." + }, + "removeMinter(address)": { + "notice": "Removes the address from the Minter list." + }, + "requestOptimisticMint(bytes32,uint32)": { + "notice": "Allows a Minter to request for an optimistic minting of TBTC. The following conditions must be met: - There is no optimistic minting request for the deposit, finalized or not. - The deposit with the given Bitcoin funding transaction hash and output index has been revealed to the Bridge. - The deposit has not been swept yet. - The deposit is targeted into the TBTCVault. - The optimistic minting is not paused. After calling this function, the Minter has to wait for `optimisticMintingDelay` before finalizing the mint with a call to finalizeOptimisticMint." + }, + "unmint(uint256)": { + "notice": "Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` back to the caller's balance in the Bank. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account." + }, + "unmintAndRedeem(uint256,bytes)": { + "notice": "Burns `amount` of TBTC from the caller's balance and transfers `amount / SATOSHI_MULTIPLIER` of Bank balance to the Bridge requesting redemption based on the provided `redemptionData`. If `amount` is not divisible by SATOSHI_MULTIPLIER, the remainder is left on the caller's account." + }, + "unpauseOptimisticMinting()": { + "notice": "Unpauses the optimistic minting." + }, + "upgradeInitiatedTimestamp()": { + "notice": "The timestamp at which an upgrade to a new TBTC vault was initiated. Set only when the upgrade process is pending." + } + }, + "notice": "TBTC is a fully Bitcoin-backed ERC-20 token pegged to the price of Bitcoin. It facilitates Bitcoin holders to act on the Ethereum blockchain and access the decentralized finance (DeFi) ecosystem. TBTC Vault mints and unmints TBTC based on Bitcoin balances in the Bank.", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 9835, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 21717, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "isOptimisticMintingPaused", + "offset": 20, + "slot": "0", + "type": "t_bool" + }, + { + "astId": 21721, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingFeeDivisor", + "offset": 21, + "slot": "0", + "type": "t_uint32" + }, + { + "astId": 21725, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingDelay", + "offset": 25, + "slot": "0", + "type": "t_uint32" + }, + { + "astId": 21730, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "isMinter", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 21734, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "minters", + "offset": 0, + "slot": "2", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 21739, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "isGuardian", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_address,t_bool)" + }, + { + "astId": 21745, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingRequests", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_uint256,t_struct(OptimisticMintingRequest)21701_storage)" + }, + { + "astId": 21750, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingDebt", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 21753, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "newOptimisticMintingFeeDivisor", + "offset": 0, + "slot": "6", + "type": "t_uint32" + }, + { + "astId": 21756, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingFeeUpdateInitiatedTimestamp", + "offset": 0, + "slot": "7", + "type": "t_uint256" + }, + { + "astId": 21759, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "newOptimisticMintingDelay", + "offset": 0, + "slot": "8", + "type": "t_uint32" + }, + { + "astId": 21762, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "optimisticMintingDelayUpdateInitiatedTimestamp", + "offset": 0, + "slot": "9", + "type": "t_uint256" + }, + { + "astId": 22633, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "newVault", + "offset": 0, + "slot": "10", + "type": "t_address" + }, + { + "astId": 22636, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "upgradeInitiatedTimestamp", + "offset": 0, + "slot": "11", + "type": "t_uint256" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "base": "t_address", + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint256,t_struct(OptimisticMintingRequest)21701_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct TBTCOptimisticMinting.OptimisticMintingRequest)", + "numberOfBytes": "32", + "value": "t_struct(OptimisticMintingRequest)21701_storage" + }, + "t_struct(OptimisticMintingRequest)21701_storage": { + "encoding": "inplace", + "label": "struct TBTCOptimisticMinting.OptimisticMintingRequest", + "members": [ + { + "astId": 21698, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "requestedAt", + "offset": 0, + "slot": "0", + "type": "t_uint64" + }, + { + "astId": 21700, + "contract": "contracts/vault/TBTCVault.sol:TBTCVault", + "label": "finalizedAt", + "offset": 8, + "slot": "0", + "type": "t_uint64" + } + ], + "numberOfBytes": "32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint32": { + "encoding": "inplace", + "label": "uint32", + "numberOfBytes": "4" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } + } +} diff --git a/typescript/src/lib/ethereum/artifacts/mainnet/WalletRegistry.json b/typescript/src/lib/ethereum/artifacts/mainnet/WalletRegistry.json new file mode 100644 index 000000000..380a69761 --- /dev/null +++ b/typescript/src/lib/ethereum/artifacts/mainnet/WalletRegistry.json @@ -0,0 +1,1962 @@ +{ + "address": "0x46d52E41C2F300BC82217Ce22b920c34995204eb", + "abi": [ + { + "inputs": [ + { + "internalType": "contract SortitionPool", + "name": "_sortitionPool", + "type": "address" + }, + { + "internalType": "contract IStaking", + "name": "_staking", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "AuthorizationDecreaseApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "decreasingAt", + "type": "uint64" + } + ], + "name": "AuthorizationDecreaseRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "AuthorizationIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint96", + "name": "minimumAuthorization", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "authorizationDecreaseDelay", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "authorizationDecreaseChangePeriod", + "type": "uint64" + } + ], + "name": "AuthorizationParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashingAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "maliciousSubmitter", + "type": "address" + } + ], + "name": "DkgMaliciousResultSlashed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashingAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "maliciousSubmitter", + "type": "address" + } + ], + "name": "DkgMaliciousResultSlashingFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "seedTimeout", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultChallengePeriodLength", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultChallengeExtraGas", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultSubmissionTimeout", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "resultSubmitterPrecedencePeriodLength", + "type": "uint256" + } + ], + "name": "DkgParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "DkgResultApproved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "challenger", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "DkgResultChallenged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "resultHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "seed", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "indexed": false, + "internalType": "struct EcdsaDkg.Result", + "name": "result", + "type": "tuple" + } + ], + "name": "DkgResultSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DkgSeedTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "seed", + "type": "uint256" + } + ], + "name": "DkgStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DkgStateLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "DkgTimedOut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "dkgResultSubmissionGas", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dkgResultApprovalGasOffset", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "notifyOperatorInactivityGasOffset", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "notifySeedTimeoutGasOffset", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "notifyDkgTimeoutNegativeGasOffset", + "type": "uint256" + } + ], + "name": "GasParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldGovernance", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "GovernanceTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "notifier", + "type": "address" + } + ], + "name": "InactivityClaimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "InvoluntaryAuthorizationDecreaseFailed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorJoinedSortitionPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "OperatorStatusUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "randomBeacon", + "type": "address" + } + ], + "name": "RandomBeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newReimbursementPool", + "type": "address" + } + ], + "name": "ReimbursementPoolUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "maliciousDkgResultNotificationRewardMultiplier", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sortitionPoolRewardsBanDuration", + "type": "uint256" + } + ], + "name": "RewardParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint96", + "name": "amount", + "type": "uint96" + } + ], + "name": "RewardsWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "maliciousDkgResultSlashingAmount", + "type": "uint256" + } + ], + "name": "SlashingParametersUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "WalletClosed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "dkgResultHash", + "type": "bytes32" + } + ], + "name": "WalletCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "walletOwner", + "type": "address" + } + ], + "name": "WalletOwnerUpdated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "relayEntry", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "__beaconCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "approveAuthorizationDecrease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "dkgResult", + "type": "tuple" + } + ], + "name": "approveDkgResult", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "authorizationDecreaseRequested", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "authorizationIncreased", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "authorizationParameters", + "outputs": [ + { + "internalType": "uint96", + "name": "minimumAuthorization", + "type": "uint96" + }, + { + "internalType": "uint64", + "name": "authorizationDecreaseDelay", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "authorizationDecreaseChangePeriod", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "availableRewards", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "dkgResult", + "type": "tuple" + } + ], + "name": "challengeDkgResult", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "closeWallet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dkgParameters", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "seedTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resultChallengePeriodLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resultChallengeExtraGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "resultSubmissionTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "submitterPrecedencePeriodLength", + "type": "uint256" + } + ], + "internalType": "struct EcdsaDkg.Parameters", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "eligibleStake", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gasParameters", + "outputs": [ + { + "internalType": "uint256", + "name": "dkgResultSubmissionGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dkgResultApprovalGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyOperatorInactivityGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifySeedTimeoutGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyDkgTimeoutNegativeGasOffset", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "getWallet", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "membersIdsHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyX", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "publicKeyY", + "type": "bytes32" + } + ], + "internalType": "struct Wallets.Wallet", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWalletCreationState", + "outputs": [ + { + "internalType": "enum EcdsaDkg.State", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "getWalletPublicKey", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "governance", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasDkgTimedOut", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "hasSeedTimedOut", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "inactivityClaimNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract EcdsaDkgValidator", + "name": "_ecdsaDkgValidator", + "type": "address" + }, + { + "internalType": "contract IRandomBeacon", + "name": "_randomBeacon", + "type": "address" + }, + { + "internalType": "contract ReimbursementPool", + "name": "_reimbursementPool", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + }, + { + "internalType": "uint96", + "name": "fromAmount", + "type": "uint96" + }, + { + "internalType": "uint96", + "name": "toAmount", + "type": "uint96" + } + ], + "name": "involuntaryAuthorizationDecrease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "result", + "type": "tuple" + } + ], + "name": "isDkgResultValid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isOperatorInPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isOperatorUpToDate", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "walletMemberIndex", + "type": "uint256" + } + ], + "name": "isWalletMember", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + } + ], + "name": "isWalletRegistered", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "joinSortitionPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "minimumAuthorization", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "notifyDkgTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "inactiveMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "heartbeatFailed", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + } + ], + "internalType": "struct EcdsaInactivity.Claim", + "name": "claim", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint32[]", + "name": "groupMembers", + "type": "uint32[]" + } + ], + "name": "notifyOperatorInactivity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "notifySeedTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "operatorToStakingProvider", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "pendingAuthorizationDecrease", + "outputs": [ + { + "internalType": "uint96", + "name": "", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "randomBeacon", + "outputs": [ + { + "internalType": "contract IRandomBeacon", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "registerOperator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reimbursementPool", + "outputs": [ + { + "internalType": "contract ReimbursementPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "remainingAuthorizationDecreaseDelay", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "requestNewWallet", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardParameters", + "outputs": [ + { + "internalType": "uint256", + "name": "maliciousDkgResultNotificationRewardMultiplier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sortitionPoolRewardsBanDuration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "amount", + "type": "uint96" + }, + { + "internalType": "uint256", + "name": "rewardMultiplier", + "type": "uint256" + }, + { + "internalType": "address", + "name": "notifier", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "walletID", + "type": "bytes32" + }, + { + "internalType": "uint32[]", + "name": "walletMembersIDs", + "type": "uint32[]" + } + ], + "name": "seize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "selectGroup", + "outputs": [ + { + "internalType": "uint32[]", + "name": "", + "type": "uint32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "slashingParameters", + "outputs": [ + { + "internalType": "uint96", + "name": "maliciousDkgResultSlashingAmount", + "type": "uint96" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sortitionPool", + "outputs": [ + { + "internalType": "contract SortitionPool", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "staking", + "outputs": [ + { + "internalType": "contract IStaking", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "stakingProviderToOperator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "submitterMemberIndex", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "groupPubKey", + "type": "bytes" + }, + { + "internalType": "uint8[]", + "name": "misbehavedMembersIndices", + "type": "uint8[]" + }, + { + "internalType": "bytes", + "name": "signatures", + "type": "bytes" + }, + { + "internalType": "uint256[]", + "name": "signingMembersIndices", + "type": "uint256[]" + }, + { + "internalType": "uint32[]", + "name": "members", + "type": "uint32[]" + }, + { + "internalType": "bytes32", + "name": "membersHash", + "type": "bytes32" + } + ], + "internalType": "struct EcdsaDkg.Result", + "name": "dkgResult", + "type": "tuple" + } + ], + "name": "submitDkgResult", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernance", + "type": "address" + } + ], + "name": "transferGovernance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "_minimumAuthorization", + "type": "uint96" + }, + { + "internalType": "uint64", + "name": "_authorizationDecreaseDelay", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "_authorizationDecreaseChangePeriod", + "type": "uint64" + } + ], + "name": "updateAuthorizationParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_seedTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_resultChallengePeriodLength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_resultChallengeExtraGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_resultSubmissionTimeout", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_submitterPrecedencePeriodLength", + "type": "uint256" + } + ], + "name": "updateDkgParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dkgResultSubmissionGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "dkgResultApprovalGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyOperatorInactivityGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifySeedTimeoutGasOffset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "notifyDkgTimeoutNegativeGasOffset", + "type": "uint256" + } + ], + "name": "updateGasParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "updateOperatorStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ReimbursementPool", + "name": "_reimbursementPool", + "type": "address" + } + ], + "name": "updateReimbursementPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "maliciousDkgResultNotificationRewardMultiplier", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sortitionPoolRewardsBanDuration", + "type": "uint256" + } + ], + "name": "updateRewardParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint96", + "name": "maliciousDkgResultSlashingAmount", + "type": "uint96" + } + ], + "name": "updateSlashingParameters", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IWalletOwner", + "name": "_walletOwner", + "type": "address" + } + ], + "name": "updateWalletOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IRandomBeacon", + "name": "_randomBeacon", + "type": "address" + } + ], + "name": "upgradeRandomBeacon", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "walletOwner", + "outputs": [ + { + "internalType": "contract IWalletOwner", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "withdrawIneligibleRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "stakingProvider", + "type": "address" + } + ], + "name": "withdrawRewards", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xe1e6a55e3026832dd8c68138e13a8ebc2be5a7350035cfdf7bf94ae55d3f5e8d", + "receipt": { + "to": null, + "from": "0x123694886DBf5Ac94DDA07135349534536D14cAf", + "contractAddress": "0x46d52E41C2F300BC82217Ce22b920c34995204eb", + "transactionIndex": 153, + "gasUsed": "1053765", + "logsBloom": "0x00000000000000000000000000000000400000004000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002000000000000000800000000000000000000000000000000000000000000000000000000800000000000000000000000000002000000200000000000000000000000000002000080000000000000800000000000000000000000000000000400000000000000000000000000000000000000000020000000000000800000040000000000000400000000000000000000000000000000000000200800000000000000000000000000000000000000000000", + "blockHash": "0x3732970bbd880c54173c7ce85b492b3590b39e450f34676090c21425058cc49b", + "transactionHash": "0xe1e6a55e3026832dd8c68138e13a8ebc2be5a7350035cfdf7bf94ae55d3f5e8d", + "logs": [ + { + "transactionIndex": 153, + "blockNumber": 15639521, + "transactionHash": "0xe1e6a55e3026832dd8c68138e13a8ebc2be5a7350035cfdf7bf94ae55d3f5e8d", + "address": "0x46d52E41C2F300BC82217Ce22b920c34995204eb", + "topics": [ + "0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b", + "0x000000000000000000000000fbae130e06bbc8ca198861beecae6e2b830398fb" + ], + "data": "0x", + "logIndex": 147, + "blockHash": "0x3732970bbd880c54173c7ce85b492b3590b39e450f34676090c21425058cc49b" + }, + { + "transactionIndex": 153, + "blockNumber": 15639521, + "transactionHash": "0xe1e6a55e3026832dd8c68138e13a8ebc2be5a7350035cfdf7bf94ae55d3f5e8d", + "address": "0x46d52E41C2F300BC82217Ce22b920c34995204eb", + "topics": [ + "0x5f56bee8cffbe9a78652a74a60705edede02af10b0bbb888ca44b79a0d42ce80" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123694886dbf5ac94dda07135349534536d14caf", + "logIndex": 148, + "blockHash": "0x3732970bbd880c54173c7ce85b492b3590b39e450f34676090c21425058cc49b" + }, + { + "transactionIndex": 153, + "blockNumber": 15639521, + "transactionHash": "0xe1e6a55e3026832dd8c68138e13a8ebc2be5a7350035cfdf7bf94ae55d3f5e8d", + "address": "0x46d52E41C2F300BC82217Ce22b920c34995204eb", + "topics": [ + "0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "logIndex": 149, + "blockHash": "0x3732970bbd880c54173c7ce85b492b3590b39e450f34676090c21425058cc49b" + }, + { + "transactionIndex": 153, + "blockNumber": 15639521, + "transactionHash": "0xe1e6a55e3026832dd8c68138e13a8ebc2be5a7350035cfdf7bf94ae55d3f5e8d", + "address": "0x46d52E41C2F300BC82217Ce22b920c34995204eb", + "topics": [ + "0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007affa05f726d293eb1193807a91617318292008e", + "logIndex": 150, + "blockHash": "0x3732970bbd880c54173c7ce85b492b3590b39e450f34676090c21425058cc49b" + } + ], + "blockNumber": 15639521, + "cumulativeGasUsed": "8679696", + "status": 1, + "byzantium": true + }, + "args": [ + "0xc2731fb2823af3Efc2694c9bC86F444d5c5bb4Dc", + "0x01B67b1194C75264d06F808A921228a95C765dd7" + ], + "numDeployments": 1, + "libraries": { + "EcdsaInactivity": "0x8263eFCb8F28246697585c89Fed0501Cd946F764" + }, + "implementation": "0xFbaE130e06Bbc8CA198861BEeCae6e2B830398fb", + "devdoc": "Contract deployed as upgradable proxy" +} diff --git a/typescript/src/lib/ethereum/bridge.ts b/typescript/src/lib/ethereum/bridge.ts index fda724628..fecf0fe25 100644 --- a/typescript/src/lib/ethereum/bridge.ts +++ b/typescript/src/lib/ethereum/bridge.ts @@ -32,13 +32,17 @@ import { } from "../bitcoin" import { EthersContractConfig, + EthersContractDeployment, EthersContractHandle, EthersTransactionUtils, } from "./adapter" -import BridgeDeployment from "@keep-network/tbtc-v2/artifacts/Bridge.json" import { EthereumAddress } from "./address" import { EthereumWalletRegistry } from "./wallet-registry" +import MainnetBridgeDeployment from "./artifacts/mainnet/Bridge.json" +import GoerliBridgeDeployment from "./artifacts/goerli/Bridge.json" +import LocalBridgeDeployment from "@keep-network/tbtc-v2/artifacts/Bridge.json" + type DepositRequestTypechain = DepositTypechain.DepositRequestStructOutput type RedemptionRequestTypechain = @@ -52,8 +56,35 @@ export class EthereumBridge extends EthersContractHandle implements Bridge { - constructor(config: EthersContractConfig) { - super(config, BridgeDeployment) + constructor( + config: EthersContractConfig, + deploymentType: "local" | "goerli" | "mainnet" = "local" + ) { + let deployment: EthersContractDeployment + + switch (deploymentType) { + case "local": + deployment = LocalBridgeDeployment + break + case "goerli": + deployment = GoerliBridgeDeployment + break + case "mainnet": + deployment = MainnetBridgeDeployment + break + default: + throw new Error("Unsupported deployment type") + } + + super(config, deployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {Bridge#getChainIdentifier} + */ + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from(this._instance.address) } // eslint-disable-next-line valid-jsdoc diff --git a/typescript/src/lib/ethereum/index.ts b/typescript/src/lib/ethereum/index.ts index 5e822f8f1..4f038294d 100644 --- a/typescript/src/lib/ethereum/index.ts +++ b/typescript/src/lib/ethereum/index.ts @@ -1,3 +1,10 @@ +import { TBTCContracts } from "../contracts" +import { providers, Signer } from "ethers" +import { EthereumBridge } from "./bridge" +import { EthereumWalletRegistry } from "./wallet-registry" +import { EthereumTBTCToken } from "./tbtc-token" +import { EthereumTBTCVault } from "./tbtc-vault" + export * from "./address" export * from "./bridge" export * from "./tbtc-token" @@ -8,3 +15,86 @@ export * from "./wallet-registry" // contains low-level contract integration code. Re-export only components // that are relevant for `lib/ethereum` clients. export { EthersContractConfig as EthereumContractConfig } from "./adapter" + +/** + * Represents an Ethereum signer. This type is a wrapper for Ethers-specific + * types and can be either a Signer that can make write transactions + * or a Provider that works only in the read-only mode. + */ +export type EthereumSigner = Signer | providers.Provider + +/** + * Resolves the Ethereum network the given Signer is tied to. + * @param signer The signer whose network should be resolved. + * @returns Ethereum network. + */ +export async function ethereumNetworkFromSigner( + signer: EthereumSigner +): Promise { + let chainId: number + if (signer instanceof Signer) { + chainId = await signer.getChainId() + } else { + const network = await signer.getNetwork() + chainId = network.chainId + } + + switch (chainId) { + case 1: + return "mainnet" + case 5: + return "goerli" + default: + return "local" + } +} + +/** + * Supported Ethereum networks. + */ +export type EthereumNetwork = "local" | "goerli" | "mainnet" + +/** + * Loads Ethereum implementation of tBTC contracts for the given Ethereum + * network and attaches the given signer there. + * @param signer Signer that should be attached to tBTC contracts. + * @param network Ethereum network. + * @returns Handle to tBTC contracts. + * @throws Throws an error if the signer's Ethereum network is other than + * the one used to load tBTC contracts. + */ +export async function loadEthereumContracts( + signer: EthereumSigner, + network: EthereumNetwork +): Promise { + const signerNetwork = await ethereumNetworkFromSigner(signer) + if (signerNetwork !== network) { + throw new Error("Signer uses different network than tBTC contracts") + } + + const bridge = new EthereumBridge({ signerOrProvider: signer }, network) + const tbtcToken = new EthereumTBTCToken({ signerOrProvider: signer }, network) + const tbtcVault = new EthereumTBTCVault({ signerOrProvider: signer }, network) + const walletRegistry = new EthereumWalletRegistry( + { signerOrProvider: signer }, + "mainnet" + ) + + const bridgeWalletRegistry = await bridge.walletRegistry() + if ( + !bridgeWalletRegistry + .getChainIdentifier() + .equals(walletRegistry.getChainIdentifier()) + ) { + throw new Error( + "Wallet registry used by Bridge is different than the one resolved from artifacts" + ) + } + + return { + bridge, + tbtcToken, + tbtcVault, + walletRegistry, + } +} diff --git a/typescript/src/lib/ethereum/tbtc-token.ts b/typescript/src/lib/ethereum/tbtc-token.ts index cb174a045..c100473cb 100644 --- a/typescript/src/lib/ethereum/tbtc-token.ts +++ b/typescript/src/lib/ethereum/tbtc-token.ts @@ -1,16 +1,20 @@ import { TBTC as TBTCTypechain } from "../../../typechain/TBTC" -import { TBTCToken } from "../contracts" +import { ChainIdentifier, TBTCToken } from "../contracts" import { BigNumber, ContractTransaction, utils } from "ethers" import { BitcoinHashUtils, BitcoinUtxo } from "../bitcoin" import { Hex } from "../utils" import { EthersContractConfig, + EthersContractDeployment, EthersContractHandle, EthersTransactionUtils, } from "./adapter" -import TBTCDeployment from "@keep-network/tbtc-v2/artifacts/TBTC.json" import { EthereumAddress } from "./address" +import MainnetTBTCTokenDeployment from "./artifacts/mainnet/TBTCToken.json" +import GoerliTBTCTokenDeployment from "./artifacts/goerli/TBTCToken.json" +import LocalTBTCTokenDeployment from "@keep-network/tbtc-v2/artifacts/TBTCToken.json" + /** * Implementation of the Ethereum TBTC v2 token handle. * @see {TBTCToken} for reference. @@ -19,8 +23,35 @@ export class EthereumTBTCToken extends EthersContractHandle implements TBTCToken { - constructor(config: EthersContractConfig) { - super(config, TBTCDeployment) + constructor( + config: EthersContractConfig, + deploymentType: "local" | "goerli" | "mainnet" = "local" + ) { + let deployment: EthersContractDeployment + + switch (deploymentType) { + case "local": + deployment = LocalTBTCTokenDeployment + break + case "goerli": + deployment = GoerliTBTCTokenDeployment + break + case "mainnet": + deployment = MainnetTBTCTokenDeployment + break + default: + throw new Error("Unsupported deployment type") + } + + super(config, deployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {TBTCToken#getChainIdentifier} + */ + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from(this._instance.address) } // eslint-disable-next-line valid-jsdoc diff --git a/typescript/src/lib/ethereum/tbtc-vault.ts b/typescript/src/lib/ethereum/tbtc-vault.ts index 7a49d3b65..1bf66b002 100644 --- a/typescript/src/lib/ethereum/tbtc-vault.ts +++ b/typescript/src/lib/ethereum/tbtc-vault.ts @@ -11,15 +11,19 @@ import { import { BigNumber, ContractTransaction } from "ethers" import { BitcoinTxHash } from "../bitcoin" import { backoffRetrier, Hex } from "../utils" -import TBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" import { EthersContractConfig, + EthersContractDeployment, EthersContractHandle, EthersTransactionUtils, } from "./adapter" import { EthereumAddress } from "./address" import { EthereumBridge } from "./bridge" +import MainnetTBTCVaultDeployment from "./artifacts/mainnet/TBTCVault.json" +import GoerliTBTCVaultDeployment from "./artifacts/goerli/TBTCVault.json" +import LocalTBTCVaultDeployment from "@keep-network/tbtc-v2/artifacts/TBTCVault.json" + type ContractOptimisticMintingRequest = { requestedAt: BigNumber finalizedAt: BigNumber @@ -33,8 +37,27 @@ export class EthereumTBTCVault extends EthersContractHandle implements TBTCVault { - constructor(config: EthersContractConfig) { - super(config, TBTCVaultDeployment) + constructor( + config: EthersContractConfig, + deploymentType: "local" | "goerli" | "mainnet" = "local" + ) { + let deployment: EthersContractDeployment + + switch (deploymentType) { + case "local": + deployment = LocalTBTCVaultDeployment + break + case "goerli": + deployment = GoerliTBTCVaultDeployment + break + case "mainnet": + deployment = MainnetTBTCVaultDeployment + break + default: + throw new Error("Unsupported deployment type") + } + + super(config, deployment) } // eslint-disable-next-line valid-jsdoc diff --git a/typescript/src/lib/ethereum/wallet-registry.ts b/typescript/src/lib/ethereum/wallet-registry.ts index a71940e78..c71621cc0 100644 --- a/typescript/src/lib/ethereum/wallet-registry.ts +++ b/typescript/src/lib/ethereum/wallet-registry.ts @@ -5,14 +5,22 @@ import { DkgResultApprovedEvent, DkgResultChallengedEvent, DkgResultSubmittedEvent, + ChainIdentifier, } from "../contracts" import { backoffRetrier, Hex } from "../utils" import { Event as EthersEvent } from "@ethersproject/contracts" import { BigNumber } from "ethers" -import WalletRegistryDeployment from "@keep-network/ecdsa/artifacts/WalletRegistry.json" -import { EthersContractConfig, EthersContractHandle } from "./adapter" +import { + EthersContractConfig, + EthersContractDeployment, + EthersContractHandle, +} from "./adapter" import { EthereumAddress } from "./address" +import MainnetWalletRegistryDeployment from "./artifacts/mainnet/WalletRegistry.json" +import GoerliWalletRegistryDeployment from "./artifacts/goerli/WalletRegistry.json" +import LocalWalletRegistryDeployment from "@keep-network/ecdsa/artifacts/WalletRegistry.json" + /** * Implementation of the Ethereum WalletRegistry handle. * @see {WalletRegistry} for reference. @@ -21,8 +29,35 @@ export class EthereumWalletRegistry extends EthersContractHandle implements WalletRegistry { - constructor(config: EthersContractConfig) { - super(config, WalletRegistryDeployment) + constructor( + config: EthersContractConfig, + deploymentType: "local" | "goerli" | "mainnet" = "local" + ) { + let deployment: EthersContractDeployment + + switch (deploymentType) { + case "local": + deployment = LocalWalletRegistryDeployment + break + case "goerli": + deployment = GoerliWalletRegistryDeployment + break + case "mainnet": + deployment = MainnetWalletRegistryDeployment + break + default: + throw new Error("Unsupported deployment type") + } + + super(config, deployment) + } + + // eslint-disable-next-line valid-jsdoc + /** + * @see {WalletRegistry#getChainIdentifier} + */ + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from(this._instance.address) } // eslint-disable-next-line valid-jsdoc diff --git a/typescript/src/services/maintenance/maintenance-service.ts b/typescript/src/services/maintenance/maintenance-service.ts index 01c55637e..913fd9225 100644 --- a/typescript/src/services/maintenance/maintenance-service.ts +++ b/typescript/src/services/maintenance/maintenance-service.ts @@ -2,7 +2,6 @@ import { TBTCContracts } from "../../lib/contracts" import { BitcoinClient } from "../../lib/bitcoin" import { OptimisticMinting } from "./optimistic-minting" import { Spv } from "./spv" -import {WalletTx} from "./wallet-tx"; /** * Service exposing features relevant to authorized maintainers and diff --git a/typescript/src/services/tbtc.ts b/typescript/src/services/tbtc.ts new file mode 100644 index 000000000..f3ca647d4 --- /dev/null +++ b/typescript/src/services/tbtc.ts @@ -0,0 +1,96 @@ +import { DepositsService } from "./deposits" +import { MaintenanceService } from "./maintenance" +import { RedemptionsService } from "./redemptions" +import { TBTCContracts } from "../lib/contracts" +import { BitcoinClient, BitcoinNetwork } from "../lib/bitcoin" +import { EthereumSigner, loadEthereumContracts } from "../lib/ethereum" +import { ElectrumClient } from "../lib/electrum" + +/** + * Entrypoint component of the tBTC v2 SDK. + */ +export class TBTC { + /** + * Service supporting the tBTC v2 deposit flow. + */ + public readonly deposits: DepositsService + /** + * Service supporting authorized operations of tBTC v2 system maintainers + * and operators. + */ + public readonly maintenance: MaintenanceService + /** + * Service supporting the tBTC v2 redemption flow. + */ + public readonly redemptions: RedemptionsService + /** + * Handle to tBTC contracts for low-level access. + */ + public readonly tbtcContracts: TBTCContracts + /** + * Bitcoin client handle for low-level access. + */ + public readonly bitcoinClient: BitcoinClient + + private constructor( + tbtcContracts: TBTCContracts, + bitcoinClient: BitcoinClient + ) { + this.deposits = new DepositsService(tbtcContracts, bitcoinClient) + this.maintenance = new MaintenanceService(tbtcContracts, bitcoinClient) + this.redemptions = new RedemptionsService(tbtcContracts, bitcoinClient) + this.tbtcContracts = tbtcContracts + this.bitcoinClient = bitcoinClient + } + + /** + * Initializes the tBTC v2 SDK entrypoint for Ethereum and Bitcoin mainnets. + * The initialized instance uses default Electrum servers to interact + * with Bitcoin mainnet + * @param signer Ethereum signer. + * @returns Initialized tBTC v2 SDK entrypoint. + * @throws Throws an error if the signer's Ethereum network is other than + * Ethereum mainnet. + */ + static async initializeMainnet(signer: EthereumSigner): Promise { + const tbtcContracts = await loadEthereumContracts(signer, "mainnet") + const bitcoinClient = ElectrumClient.fromDefaultConfig( + BitcoinNetwork.Mainnet + ) + return new TBTC(tbtcContracts, bitcoinClient) + } + + /** + * Initializes the tBTC v2 SDK entrypoint for Ethereum goerli and Bitcoin testnet. + * The initialized instance uses default Electrum servers to interact + * with Bitcoin testnet + * @param signer Ethereum signer. + * @returns Initialized tBTC v2 SDK entrypoint. + * @throws Throws an error if the signer's Ethereum network is other than + * Ethereum mainnet. + */ + static async initializeGoerli(signer: EthereumSigner): Promise { + const tbtcContracts = await loadEthereumContracts(signer, "goerli") + const bitcoinClient = ElectrumClient.fromDefaultConfig( + BitcoinNetwork.Testnet + ) + return new TBTC(tbtcContracts, bitcoinClient) + } + + /** + * Initializes the tBTC v2 SDK entrypoint with custom tBTC contracts and + * Bitcoin client. + * @param tbtcContracts Custom tBTC contracts handle. + * @param bitcoinClient Custom Bitcoin client implementation. + * @returns Initialized tBTC v2 SDK entrypoint. + * @dev This function is especially useful for local development as it gives + * flexibility to combine different implementations of tBTC v2 contracts + * with different Bitcoin networks. + */ + static async initializeCustom( + tbtcContracts: TBTCContracts, + bitcoinClient: BitcoinClient + ): Promise { + return new TBTC(tbtcContracts, bitcoinClient) + } +} diff --git a/typescript/test/utils/mock-bridge.ts b/typescript/test/utils/mock-bridge.ts index 8e7ecc118..f6fbd0757 100644 --- a/typescript/test/utils/mock-bridge.ts +++ b/typescript/test/utils/mock-bridge.ts @@ -378,4 +378,8 @@ export class MockBridge implements Bridge { ): Promise { throw new Error("not implemented") } + + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from("0x894cfd89700040163727828AE20B52099C58F02C") + } } diff --git a/typescript/test/utils/mock-tbtc-token.ts b/typescript/test/utils/mock-tbtc-token.ts index 29ba32497..b2238f550 100644 --- a/typescript/test/utils/mock-tbtc-token.ts +++ b/typescript/test/utils/mock-tbtc-token.ts @@ -1,7 +1,8 @@ -import { TBTCToken } from "../../src/lib/contracts" +import { ChainIdentifier, TBTCToken } from "../../src/lib/contracts" import { Hex } from "../../src/lib/utils" import { BigNumber } from "ethers" import { BitcoinUtxo } from "../../src/lib/bitcoin" +import { EthereumAddress } from "../../src" interface RequestRedemptionLog { walletPublicKey: string @@ -38,4 +39,8 @@ export class MockTBTCToken implements TBTCToken { "0xf7d0c92c8de4d117d915c2a8a54ee550047f926bc00b91b651c40628751cfe29" ) } + + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from("0x694cfd89700040163727828AE20B52099C58F02C") + } } diff --git a/typescript/test/utils/mock-wallet-registry.ts b/typescript/test/utils/mock-wallet-registry.ts index 708c6fb00..2acc1c464 100644 --- a/typescript/test/utils/mock-wallet-registry.ts +++ b/typescript/test/utils/mock-wallet-registry.ts @@ -4,8 +4,10 @@ import { DkgResultApprovedEvent, DkgResultChallengedEvent, DkgResultSubmittedEvent, + ChainIdentifier, } from "../../src/lib/contracts" import { Hex } from "../../src/lib/utils" +import { EthereumAddress } from "../../src" export class MockWalletRegistry implements WalletRegistry { getDkgResultApprovedEvents( @@ -32,4 +34,8 @@ export class MockWalletRegistry implements WalletRegistry { getWalletPublicKey(walletID: Hex): Promise { throw new Error("not implemented") } + + getChainIdentifier(): ChainIdentifier { + return EthereumAddress.from("0x794cfd89700040163727828AE20B52099C58F02C") + } } From 5c74ca679515d836d9ad6d46847db578d350ecd6 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Wed, 4 Oct 2023 14:33:15 +0200 Subject: [PATCH 23/43] Facilitate deposit initiation Here we get rid of the `depositor` parameter that must be passed by the caller in favor of a default depositor set upon deposit service creation. This should facilitate the public API of the deposit service and make it more friendly for 3rd party integrators. --- typescript/src/lib/ethereum/index.ts | 21 +++++++- .../src/services/deposits/deposits-service.ts | 51 ++++++++++++------- typescript/src/services/tbtc.ts | 49 ++++++++++++++---- 3 files changed, 91 insertions(+), 30 deletions(-) diff --git a/typescript/src/lib/ethereum/index.ts b/typescript/src/lib/ethereum/index.ts index 4f038294d..d61c7eaad 100644 --- a/typescript/src/lib/ethereum/index.ts +++ b/typescript/src/lib/ethereum/index.ts @@ -4,6 +4,7 @@ import { EthereumBridge } from "./bridge" import { EthereumWalletRegistry } from "./wallet-registry" import { EthereumTBTCToken } from "./tbtc-token" import { EthereumTBTCVault } from "./tbtc-vault" +import { EthereumAddress } from "./address" export * from "./address" export * from "./bridge" @@ -24,7 +25,7 @@ export { EthersContractConfig as EthereumContractConfig } from "./adapter" export type EthereumSigner = Signer | providers.Provider /** - * Resolves the Ethereum network the given Signer is tied to. + * Resolves the Ethereum network the given signer is tied to. * @param signer The signer whose network should be resolved. * @returns Ethereum network. */ @@ -49,6 +50,24 @@ export async function ethereumNetworkFromSigner( } } +/** + * Resolves the Ethereum address tied to the given signer. The address + * cannot be resolved for signers that works in the read-only mode + * @param signer The signer whose address should be resolved. + * @returns Ethereum address or undefined for read-only signers. + * @throws Throws an error if the address of the signer is not a proper + * Ethereum address. + */ +export async function ethereumAddressFromSigner( + signer: EthereumSigner +): Promise { + if (signer instanceof Signer) { + return EthereumAddress.from(await signer.getAddress()) + } else { + return undefined + } +} + /** * Supported Ethereum networks. */ diff --git a/typescript/src/services/deposits/deposits-service.ts b/typescript/src/services/deposits/deposits-service.ts index d7c5c73b5..9c8ef01d5 100644 --- a/typescript/src/services/deposits/deposits-service.ts +++ b/typescript/src/services/deposits/deposits-service.ts @@ -29,6 +29,11 @@ export class DepositsService { * Bitcoin client handle. */ private readonly bitcoinClient: BitcoinClient + /** + * Chain-specific identifier of the default depositor used for deposits + * initiated by this service. + */ + private defaultDepositor: ChainIdentifier | undefined constructor(tbtcContracts: TBTCContracts, bitcoinClient: BitcoinClient) { this.tbtcContracts = tbtcContracts @@ -36,34 +41,31 @@ export class DepositsService { } /** - * Initiates a new deposit for the given depositor and Bitcoin recovery address. - * @param depositor Identifier of the depositor specific for the target chain - * deposited BTC are bridged to. For example, this is a - * 20-byte address on Ethereum. + * Initiates the tBTC v2 deposit process. * @param bitcoinRecoveryAddress P2PKH or P2WPKH Bitcoin address that can * be used for emergency recovery of the * deposited funds. - * @returns Handle to the initiated deposit. + * @returns Handle to the initiated deposit process. + * @throws Throws an error if one of the following occurs: + * - The default depositor is not set + * - There are no active wallet in the Bridge contract + * - The Bitcoin recovery address is not a valid P2(W)PKH */ - // TODO: Accept depositor as string and automatically validate & convert OR - // explore the possibility of fetching this from the account instance. // TODO: Cover with unit tests. - async initiateDeposit( - depositor: ChainIdentifier, - bitcoinRecoveryAddress: string - ): Promise { - const receipt = await this.generateDepositReceipt( - depositor, - bitcoinRecoveryAddress - ) - + async initiateDeposit(bitcoinRecoveryAddress: string): Promise { + const receipt = await this.generateDepositReceipt(bitcoinRecoveryAddress) return Deposit.fromReceipt(receipt, this.tbtcContracts, this.bitcoinClient) } private async generateDepositReceipt( - depositor: ChainIdentifier, bitcoinRecoveryAddress: string ): Promise { + if (this.defaultDepositor === undefined) { + throw new Error( + "Default depositor is not set; use setDefaultDepositor first" + ) + } + const blindingFactor = crypto.randomBytes(8).toString("hex") const walletPublicKey = @@ -90,11 +92,24 @@ export class DepositsService { ) return { - depositor, + depositor: this.defaultDepositor, blindingFactor, walletPublicKeyHash, refundPublicKeyHash, refundLocktime, } } + + // eslint-disable-next-line valid-jsdoc + /** + * Sets the default depositor used for deposits initiated by this service. + * @param defaultDepositor Chain-specific identifier of the default depositor. + * @dev Typically, there is no need to use this method when DepositsService + * is orchestrated automatically. However, there are some use cases + * where setting the default depositor explicitly may be useful. + * Make sure you know what you are doing while using this method. + */ + setDefaultDepositor(defaultDepositor: ChainIdentifier) { + this.defaultDepositor = defaultDepositor + } } diff --git a/typescript/src/services/tbtc.ts b/typescript/src/services/tbtc.ts index f3ca647d4..abb7bd9de 100644 --- a/typescript/src/services/tbtc.ts +++ b/typescript/src/services/tbtc.ts @@ -3,7 +3,12 @@ import { MaintenanceService } from "./maintenance" import { RedemptionsService } from "./redemptions" import { TBTCContracts } from "../lib/contracts" import { BitcoinClient, BitcoinNetwork } from "../lib/bitcoin" -import { EthereumSigner, loadEthereumContracts } from "../lib/ethereum" +import { + ethereumAddressFromSigner, + EthereumNetwork, + EthereumSigner, + loadEthereumContracts, +} from "../lib/ethereum" import { ElectrumClient } from "../lib/electrum" /** @@ -53,11 +58,7 @@ export class TBTC { * Ethereum mainnet. */ static async initializeMainnet(signer: EthereumSigner): Promise { - const tbtcContracts = await loadEthereumContracts(signer, "mainnet") - const bitcoinClient = ElectrumClient.fromDefaultConfig( - BitcoinNetwork.Mainnet - ) - return new TBTC(tbtcContracts, bitcoinClient) + return TBTC.initializeEthereum(signer, "mainnet", BitcoinNetwork.Mainnet) } /** @@ -70,11 +71,37 @@ export class TBTC { * Ethereum mainnet. */ static async initializeGoerli(signer: EthereumSigner): Promise { - const tbtcContracts = await loadEthereumContracts(signer, "goerli") - const bitcoinClient = ElectrumClient.fromDefaultConfig( - BitcoinNetwork.Testnet - ) - return new TBTC(tbtcContracts, bitcoinClient) + return TBTC.initializeEthereum(signer, "goerli", BitcoinNetwork.Testnet) + } + + /** + * Initializes the tBTC v2 SDK entrypoint for the given Ethereum network and Bitcoin network. + * The initialized instance uses default Electrum servers to interact + * with Bitcoin network. + * @param signer Ethereum signer. + * @param ethereumNetwork Ethereum network. + * @param bitcoinNetwork Bitcoin network. + * @returns Initialized tBTC v2 SDK entrypoint. + * @throws Throws an error if the underlying signer's Ethereum network is + * other than the given Ethereum network. + */ + private static async initializeEthereum( + signer: EthereumSigner, + ethereumNetwork: EthereumNetwork, + bitcoinNetwork: BitcoinNetwork + ): Promise { + const signerAddress = await ethereumAddressFromSigner(signer) + const tbtcContracts = await loadEthereumContracts(signer, ethereumNetwork) + const bitcoinClient = ElectrumClient.fromDefaultConfig(bitcoinNetwork) + + const tbtc = new TBTC(tbtcContracts, bitcoinClient) + + // If signer address can be resolved, set it as default depositor. + if (signerAddress !== undefined) { + tbtc.deposits.setDefaultDepositor(signerAddress) + } + + return tbtc } /** From 64ea5390c1344a896ba204028668a979e9cef237 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 5 Oct 2023 13:40:32 +0200 Subject: [PATCH 24/43] Remove `src` and `typechain` from result package Those directories are not needed as everything is included in `dist` --- typescript/package.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/typescript/package.json b/typescript/package.json index 0f803bdc4..27e8a36a4 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -15,11 +15,7 @@ "build": "npm run typechain && tsc --project tsconfig.build.json", "dev": "tsc --project tsconfig.build.json --watch" }, - "files": [ - "dist/", - "src/", - "typechain/" - ], + "files": ["dist"], "config": { "contracts": "./node_modules/@keep-network/ecdsa/artifacts/WalletRegistry.json ./node_modules/@keep-network/tbtc-v2/artifacts/{Bridge,TBTCVault,TBTC}.json" }, From cc3d85a5370723aff82fa079a94e6135c968313b Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Thu, 5 Oct 2023 14:07:19 +0200 Subject: [PATCH 25/43] Sort out and update TODOs Remove the ones that are no longer relevant and make the existing ones reflect real work that needs to be done. --- typescript/src/index.ts | 3 +++ typescript/src/lib/contracts/bridge.ts | 5 +---- typescript/src/lib/contracts/tbtc-token.ts | 2 -- typescript/src/lib/contracts/tbtc-vault.ts | 4 ---- typescript/src/lib/ethereum/adapter.ts | 2 -- typescript/src/lib/ethereum/address.ts | 4 ++-- typescript/src/services/deposits/deposit.ts | 1 - typescript/src/services/deposits/deposits-service.ts | 2 +- typescript/src/services/maintenance/spv.ts | 6 ------ typescript/src/services/maintenance/wallet-tx.ts | 5 ----- typescript/test/deposit.test.ts | 4 +++- typescript/test/electrum.test.ts | 5 +++-- 12 files changed, 13 insertions(+), 30 deletions(-) diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 2ee4bcafd..628275200 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -12,3 +12,6 @@ export * from "./services/redemptions" // Export the entrypoint module. export * from "./services/tbtc" + +// TODO: Replace all properties that are expected to be un-prefixed hexadecimal +// strings with a Hex type to increase API consistency. diff --git a/typescript/src/lib/contracts/bridge.ts b/typescript/src/lib/contracts/bridge.ts index d78499c55..0e26c1587 100644 --- a/typescript/src/lib/contracts/bridge.ts +++ b/typescript/src/lib/contracts/bridge.ts @@ -55,7 +55,7 @@ export interface Bridge { depositOutputIndex: number, deposit: DepositReceipt, vault?: ChainIdentifier - ): Promise // TODO: Update to Hex + ): Promise /** * Gets a revealed deposit from the on-chain contract. @@ -181,9 +181,6 @@ export interface Bridge { getRedemptionRequestedEvents: GetChainEvents.Function } -// TODO: Replace all properties that are expected to be un-prefixed hexadecimal -// strings with a Hex type. - /** * Represents a deposit receipt. The receipt holds all information required * to build a unique deposit address on Bitcoin chain. diff --git a/typescript/src/lib/contracts/tbtc-token.ts b/typescript/src/lib/contracts/tbtc-token.ts index b78ef1823..596c88ddb 100644 --- a/typescript/src/lib/contracts/tbtc-token.ts +++ b/typescript/src/lib/contracts/tbtc-token.ts @@ -20,8 +20,6 @@ export interface TBTCToken { * supply should be fetched for. If this parameter is not set, the * total supply is taken for the latest block. */ - // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 - // precision to Bitcoin in 1e8 precision (satoshi). totalSupply(blockNumber?: number): Promise /** diff --git a/typescript/src/lib/contracts/tbtc-vault.ts b/typescript/src/lib/contracts/tbtc-vault.ts index 277e445ff..1ddb131d0 100644 --- a/typescript/src/lib/contracts/tbtc-vault.ts +++ b/typescript/src/lib/contracts/tbtc-vault.ts @@ -152,8 +152,6 @@ export type OptimisticMintingRequestedEvent = { * This value is in ERC 1e18 precision, it has to be converted before using * as Bitcoin value with 1e8 precision in satoshi. */ - // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 - // precision to Bitcoin in 1e8 precision (satoshi). amount: BigNumber /** * Hash of a Bitcoin transaction made to fund the deposit. @@ -204,7 +202,5 @@ export type OptimisticMintingFinalizedEvent = { * This value is in ERC 1e18 precision, it has to be converted before using * as Bitcoin value with 1e8 precision in satoshi. */ - // TODO: Consider adding a custom type to handle conversion from ERC with 1e18 - // precision to Bitcoin in 1e8 precision (satoshi). optimisticMintingDebt: BigNumber } & ChainEvent diff --git a/typescript/src/lib/ethereum/adapter.ts b/typescript/src/lib/ethereum/adapter.ts index 8a678acc3..0f678c26e 100644 --- a/typescript/src/lib/ethereum/adapter.ts +++ b/typescript/src/lib/ethereum/adapter.ts @@ -129,8 +129,6 @@ export class EthersContractHandle { options?: GetChainEvents.Options, ...filterArgs: Array ): Promise { - // TODO: Test if we need a workaround for querying events from big range in chunks, - // see: https://github.com/keep-network/tbtc-monitoring/blob/e169357d7b8c638d4eaf73d52aa8f53ee4aebc1d/src/lib/ethereum-helper.js#L44-L73 return backoffRetrier( options?.retries ?? this._totalRetryAttempts )(async () => { diff --git a/typescript/src/lib/ethereum/address.ts b/typescript/src/lib/ethereum/address.ts index 9135dddd0..a5bbb30a7 100644 --- a/typescript/src/lib/ethereum/address.ts +++ b/typescript/src/lib/ethereum/address.ts @@ -4,7 +4,8 @@ import { utils } from "ethers" /** * Represents an Ethereum address. */ -// TODO: Make Address extends Hex +// TODO: Make EthereumAddress extends Hex. Remember about keeping the address +// validation while creating EthereumAddress instance. export class EthereumAddress implements ChainIdentifier { readonly identifierHex: string @@ -24,7 +25,6 @@ export class EthereumAddress implements ChainIdentifier { return new EthereumAddress(address) } - // TODO: Remove once extends Hex equals(otherValue: EthereumAddress): boolean { return this.identifierHex === otherValue.identifierHex } diff --git a/typescript/src/services/deposits/deposit.ts b/typescript/src/services/deposits/deposit.ts index e455724ad..4ce3099a3 100644 --- a/typescript/src/services/deposits/deposit.ts +++ b/typescript/src/services/deposits/deposit.ts @@ -111,7 +111,6 @@ export class Deposit { * initiate minting (both modes). */ // TODO: Cover auto funding outpoint detection with unit tests. - // TODO: Return Hex. async initiateMinting(fundingOutpoint?: BitcoinTxOutpoint): Promise { let resolvedFundingOutpoint: BitcoinTxOutpoint diff --git a/typescript/src/services/deposits/deposits-service.ts b/typescript/src/services/deposits/deposits-service.ts index 9c8ef01d5..eb985d7c1 100644 --- a/typescript/src/services/deposits/deposits-service.ts +++ b/typescript/src/services/deposits/deposits-service.ts @@ -78,7 +78,7 @@ export class DepositsService { const walletPublicKeyHash = BitcoinHashUtils.computeHash160(walletPublicKey) // TODO: Only P2(W)PKH addresses can be used for recovery. The below conversion - // function ensures that but it would be good to check it here as well + // function ensures that but, it would be good to check it here as well // in case the converter implementation changes. const refundPublicKeyHash = BitcoinAddressConverter.addressToPublicKeyHash( bitcoinRecoveryAddress diff --git a/typescript/src/services/maintenance/spv.ts b/typescript/src/services/maintenance/spv.ts index d64fef5b9..c00d690db 100644 --- a/typescript/src/services/maintenance/spv.ts +++ b/typescript/src/services/maintenance/spv.ts @@ -42,9 +42,6 @@ export class Spv { confirmations, this.bitcoinClient ) - // TODO: Write a converter and use it to convert the transaction part of the - // proof to the decomposed transaction data (version, inputs, outputs, locktime). - // Use raw transaction data for now. const rawTransaction = await this.bitcoinClient.getRawTransaction( transactionHash ) @@ -78,9 +75,6 @@ export class Spv { confirmations, this.bitcoinClient ) - // TODO: Write a converter and use it to convert the transaction part of the - // proof to the decomposed transaction data (version, inputs, outputs, locktime). - // Use raw transaction data for now. const rawTransaction = await this.bitcoinClient.getRawTransaction( transactionHash ) diff --git a/typescript/src/services/maintenance/wallet-tx.ts b/typescript/src/services/maintenance/wallet-tx.ts index 4c2522e98..970f50574 100644 --- a/typescript/src/services/maintenance/wallet-tx.ts +++ b/typescript/src/services/maintenance/wallet-tx.ts @@ -603,11 +603,6 @@ class Redemption { totalOutputsValue = totalOutputsValue.add(outputValue) // Add the fee for this particular request to the overall transaction fee - // TODO: Using the maximum allowed transaction fee for the request (`txMaxFee`) - // as the transaction fee for now. In the future allow the caller to - // propose the value of the transaction fee. If the proposed transaction - // fee is smaller than the sum of fee shares from all the outputs then - // use the proposed fee and add the difference to outputs proportionally. txTotalFee = txTotalFee.add(request.txMaxFee) transaction.addOutput({ diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index 45887102d..d534d37e7 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -650,7 +650,9 @@ describe("Deposit", () => { }) describe("Deposit", () => { - describe("", () => { + // TODO: Implement unit tests for other functions. + + describe("initiateMinting", () => { describe("auto funding outpoint detection mode", () => { // TODO: Unit test for this case. }) diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index eccfe1056..6d8ff8730 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -18,7 +18,7 @@ import https from "https" const BLOCKSTREAM_TESTNET_API_URL = "https://blockstream.info/testnet/api" const testnetCredentials: ElectrumCredentials[] = [ - // TODO: Enable all protocols test for test.tbtc.network servers once they are + // FIXME: Enable all protocols test for test.tbtc.network servers once they are // publicly exposed. // // electrumx tcp // { @@ -138,7 +138,8 @@ describe.skip("Electrum", () => { ) expect(result).to.be.eql(testnetTransaction) }) - // TODO: Add case when transaction doesn't exist + + // TODO: Add case when transaction doesn't exist. }) describe("getRawTransaction", () => { From 66cbee185df5fbbe912a7e2b52fa2edcfec2868c Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 9 Oct 2023 11:39:21 +0200 Subject: [PATCH 26/43] Add `bitcoinjs-lib` and friends --- typescript/package.json | 11 +- typescript/yarn.lock | 238 ++++++++++++++++++++++++++++++++++------ 2 files changed, 212 insertions(+), 37 deletions(-) diff --git a/typescript/package.json b/typescript/package.json index 27e8a36a4..fb929012a 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -13,7 +13,8 @@ "test": "mocha --exit --recursive 'test/**/*.test.ts'", "typechain": "rm -rf ./typechain && for i in $npm_package_config_contracts; do typechain --target ethers-v5 --out-dir ./typechain $i; done && rm ./typechain/index.ts", "build": "npm run typechain && tsc --project tsconfig.build.json", - "dev": "tsc --project tsconfig.build.json --watch" + "dev": "tsc --project tsconfig.build.json --watch", + "postinstall": "npm rebuild bcrypto" }, "files": ["dist"], "config": { @@ -22,7 +23,8 @@ "dependencies": { "@keep-network/ecdsa": "development", "@keep-network/tbtc-v2": "development", - "bitcoinjs-lib": "^6.1.5", + "bcoin": "git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8", + "bitcoinjs-lib": "6.0.2", "bufio": "^1.0.6", "ecpair": "^2.1.0", "electrum-client-js": "git+https://github.com/keep-network/electrum-client-js.git#v0.1.1", @@ -54,6 +56,9 @@ "typescript": "^4.3.5" }, "engines": { - "node": ">=16" + "node": ">=14 <15" + }, + "browser": { + "bcoin": "bcoin/lib/bcoin-browser" } } diff --git a/typescript/yarn.lock b/typescript/yarn.lock index 08928f090..05897068f 100644 --- a/typescript/yarn.lock +++ b/typescript/yarn.lock @@ -1643,11 +1643,6 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== -"@noble/hashes@^1.2.0": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" - integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== - "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2496,16 +2491,50 @@ base-x@^3.0.2, base-x@^3.0.8: dependencies: safe-buffer "^5.0.1" -base-x@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" - integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== - base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +"bcfg@git+https://github.com/bcoin-org/bcfg.git#semver:~0.1.7": + version "0.1.7" + resolved "git+https://github.com/bcoin-org/bcfg.git#05122154b35baa82cd01dc9478ebee7346386ba1" + dependencies: + bsert "~0.0.10" + +"bcoin@git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8": + version "2.2.0" + resolved "git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8" + dependencies: + bcfg "git+https://github.com/bcoin-org/bcfg.git#semver:~0.1.7" + bcrypto "git+https://github.com/bcoin-org/bcrypto.git#semver:~5.5.0" + bcurl "git+https://github.com/bcoin-org/bcurl.git#semver:^0.1.6" + bdb "git+https://github.com/bcoin-org/bdb.git#semver:~1.2.1" + bdns "git+https://github.com/bcoin-org/bdns.git#semver:~0.1.5" + bevent "git+https://github.com/bcoin-org/bevent.git#semver:~0.1.5" + bfile "git+https://github.com/bcoin-org/bfile.git#semver:~0.2.1" + bfilter "git+https://github.com/bcoin-org/bfilter.git#semver:~2.3.0" + bheep "git+https://github.com/bcoin-org/bheep.git#semver:~0.1.5" + binet "git+https://github.com/bcoin-org/binet.git#semver:~0.3.5" + blgr "git+https://github.com/bcoin-org/blgr.git#semver:~0.2.0" + blru "git+https://github.com/bcoin-org/blru.git#semver:~0.1.6" + blst "git+https://github.com/bcoin-org/blst.git#semver:~0.1.5" + bmutex "git+https://github.com/bcoin-org/bmutex.git#semver:~0.1.6" + brq "git+https://github.com/bcoin-org/brq.git#semver:~0.1.7" + bs32 "git+https://github.com/bcoin-org/bs32.git#semver:=0.1.6" + bsert "git+https://github.com/chjj/bsert.git#semver:~0.0.10" + bsock "git+https://github.com/bcoin-org/bsock.git#semver:~0.1.9" + bsocks "git+https://github.com/bcoin-org/bsocks.git#semver:~0.2.6" + btcp "git+https://github.com/bcoin-org/btcp.git#semver:~0.1.5" + buffer-map "git+https://github.com/chjj/buffer-map.git#semver:~0.0.7" + bufio "git+https://github.com/bcoin-org/bufio.git#semver:~1.0.6" + bupnp "git+https://github.com/bcoin-org/bupnp.git#semver:~0.2.6" + bval "git+https://github.com/bcoin-org/bval.git#semver:~0.1.6" + bweb "git+https://github.com/bcoin-org/bweb.git#semver:=0.1.9" + loady "git+https://github.com/chjj/loady.git#semver:~0.0.1" + n64 "git+https://github.com/chjj/n64.git#semver:~0.2.10" + nan "git+https://github.com/braydonf/nan.git#semver:=2.14.0" + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -2513,6 +2542,34 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +"bcrypto@git+https://github.com/bcoin-org/bcrypto.git#semver:~5.5.0": + version "5.5.0" + resolved "git+https://github.com/bcoin-org/bcrypto.git#34738cf15033e3bce91a4f6f41ec1ebee3c2fdc8" + dependencies: + bufio "~1.0.7" + loady "~0.0.5" + +"bcurl@git+https://github.com/bcoin-org/bcurl.git#semver:^0.1.6": + version "0.1.10" + resolved "git+https://github.com/bcoin-org/bcurl.git#d7e088fad4c284fb5d6fd7205c6b903bd3e6bf83" + dependencies: + brq "~0.1.8" + bsert "~0.0.10" + bsock "~0.1.9" + +"bdb@git+https://github.com/bcoin-org/bdb.git#semver:~1.2.1": + version "1.2.2" + resolved "git+https://github.com/bcoin-org/bdb.git#2c8d48c8adca4b11260263472766cd4b7ae74ef7" + dependencies: + bsert "~0.0.10" + loady "~0.0.1" + +"bdns@git+https://github.com/bcoin-org/bdns.git#semver:~0.1.5": + version "0.1.5" + resolved "git+https://github.com/bcoin-org/bdns.git#cb0b62a0075f7e1259fc50fa723ba644e9a07d14" + dependencies: + bsert "~0.0.10" + bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -2523,6 +2580,31 @@ bech32@^2.0.0: resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== +"bevent@git+https://github.com/bcoin-org/bevent.git#semver:~0.1.5": + version "0.1.5" + resolved "git+https://github.com/bcoin-org/bevent.git#60fb503de3ea1292d29ce438bfba80f0bc5ccb60" + dependencies: + bsert "~0.0.10" + +"bfile@git+https://github.com/bcoin-org/bfile.git#semver:~0.2.1": + version "0.2.2" + resolved "git+https://github.com/bcoin-org/bfile.git#c3075133a02830dc384f8353d8275d4499b8bff9" + +"bfilter@git+https://github.com/bcoin-org/bfilter.git#semver:~2.3.0": + version "2.3.0" + resolved "git+https://github.com/bcoin-org/bfilter.git#70e42125f877191d340e8838a1a90fabb750e680" + dependencies: + bcrypto "git+https://github.com/bcoin-org/bcrypto.git#semver:~5.5.0" + bsert "git+https://github.com/chjj/bsert.git#semver:~0.0.10" + bufio "git+https://github.com/bcoin-org/bufio.git#semver:~1.0.6" + loady "git+https://github.com/chjj/loady.git#semver:~0.0.1" + +"bheep@git+https://github.com/bcoin-org/bheep.git#semver:~0.1.5": + version "0.1.5" + resolved "git+https://github.com/bcoin-org/bheep.git#e59329d0a776ae71b2fb7a2876ee5b9fd3030fa2" + dependencies: + bsert "~0.0.10" + big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -2555,7 +2637,14 @@ bindings@^1.3.0: dependencies: file-uri-to-path "1.0.0" -bip174@^2.1.1: +"binet@git+https://github.com/bcoin-org/binet.git#semver:~0.3.5", binet@~0.3.5: + version "0.3.6" + resolved "git+https://github.com/bcoin-org/binet.git#d3decfb7a7257abdfb03c3a9c091499b2ebff0e1" + dependencies: + bs32 "~0.1.5" + bsert "~0.0.10" + +bip174@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== @@ -2602,17 +2691,19 @@ bip39@3.0.4: pbkdf2 "^3.0.9" randombytes "^2.0.1" -bitcoinjs-lib@^6.1.5: - version "6.1.5" - resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz#3b03509ae7ddd80a440f10fc38c4a97f0a028d8c" - integrity sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ== +bitcoinjs-lib@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.0.2.tgz#0fdf6c41978d93641b936d66f4afce44bb9b7f35" + integrity sha512-I994pGt9cL5s5OA6mkv1e8IuYcsKN2ORXnWbkqAXLNGvEnOHBhKBSvCjFl7YC2uVoJnfr/iwq7JMrq575SYO5w== dependencies: - "@noble/hashes" "^1.2.0" bech32 "^2.0.0" - bip174 "^2.1.1" - bs58check "^3.0.1" + bip174 "^2.0.1" + bs58check "^2.1.2" + create-hash "^1.1.0" + ripemd160 "^2.0.2" typeforce "^1.11.3" varuint-bitcoin "^1.1.2" + wif "^2.0.1" bl@^1.0.0: version "1.2.3" @@ -2627,6 +2718,18 @@ blakejs@^1.1.0: resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== +"blgr@git+https://github.com/bcoin-org/blgr.git#semver:~0.2.0": + version "0.2.0" + resolved "git+https://github.com/bcoin-org/blgr.git#050cbb587a1654a078468dbb92606330fdc4d120" + dependencies: + bsert "~0.0.10" + +"blru@git+https://github.com/bcoin-org/blru.git#semver:~0.1.6": + version "0.1.6" + resolved "git+https://github.com/bcoin-org/blru.git#c2c093e9475439333dfb87bfb2fdc3be6c98b080" + dependencies: + bsert "~0.0.10" + "bls12377js@https://github.com/celo-org/bls12377js#400bcaeec9e7620b040bfad833268f5289699cac": version "0.1.0" resolved "https://github.com/celo-org/bls12377js#400bcaeec9e7620b040bfad833268f5289699cac" @@ -2651,11 +2754,23 @@ blakejs@^1.1.0: ts-node "^8.4.1" typescript "^3.6.4" +"blst@git+https://github.com/bcoin-org/blst.git#semver:~0.1.5": + version "0.1.5" + resolved "git+https://github.com/bcoin-org/blst.git#d588403edb18e628899e05aeba8c3a98a5cdedff" + dependencies: + bsert "~0.0.10" + bluebird@^3.5.0, bluebird@^3.5.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +"bmutex@git+https://github.com/bcoin-org/bmutex.git#semver:~0.1.6": + version "0.1.6" + resolved "git+https://github.com/bcoin-org/bmutex.git#e50782323932a4946ecc05a74c6d45861adc2c25" + dependencies: + bsert "~0.0.10" + bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" @@ -2778,6 +2893,18 @@ browserify-sign@^4.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" +"brq@git+https://github.com/bcoin-org/brq.git#semver:~0.1.7", brq@~0.1.7, brq@~0.1.8: + version "0.1.8" + resolved "git+https://github.com/bcoin-org/brq.git#534bb2c83fb366ba40ad80bc3de796a174503294" + dependencies: + bsert "~0.0.10" + +"bs32@git+https://github.com/bcoin-org/bs32.git#semver:=0.1.6", bs32@~0.1.5: + version "0.1.6" + resolved "git+https://github.com/bcoin-org/bs32.git#21cf9c724659dc15df722d2410548828c142f265" + dependencies: + bsert "~0.0.10" + bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -2785,13 +2912,6 @@ bs58@^4.0.0: dependencies: base-x "^3.0.2" -bs58@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" - integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== - dependencies: - base-x "^4.0.0" - bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" @@ -2801,13 +2921,26 @@ bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: create-hash "^1.1.0" safe-buffer "^5.1.2" -bs58check@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c" - integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ== +"bsert@git+https://github.com/chjj/bsert.git#semver:~0.0.10", bsert@~0.0.10: + version "0.0.10" + resolved "git+https://github.com/chjj/bsert.git#bd09d49eab8644bca08ae8259a3d8756e7d453fc" + +"bsock@git+https://github.com/bcoin-org/bsock.git#semver:~0.1.9", bsock@~0.1.8, bsock@~0.1.9: + version "0.1.9" + resolved "git+https://github.com/bcoin-org/bsock.git#7cf76b3021ae7929c023d1170f789811e91ae528" dependencies: - "@noble/hashes" "^1.2.0" - bs58 "^5.0.0" + bsert "~0.0.10" + +"bsocks@git+https://github.com/bcoin-org/bsocks.git#semver:~0.2.6": + version "0.2.6" + resolved "git+https://github.com/bcoin-org/bsocks.git#6a8eb764dc4408e7f47da4f84e1afb1b393117e8" + dependencies: + binet "~0.3.5" + bsert "~0.0.10" + +"btcp@git+https://github.com/bcoin-org/btcp.git#semver:~0.1.5": + version "0.1.5" + resolved "git+https://github.com/bcoin-org/btcp.git#4ea7e1ce5a43cd5348152c007aff76a419190a3a" buffer-alloc-unsafe@^1.1.0: version "1.1.0" @@ -2837,6 +2970,10 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +"buffer-map@git+https://github.com/chjj/buffer-map.git#semver:~0.0.7": + version "0.0.7" + resolved "git+https://github.com/chjj/buffer-map.git#bad5863af9a520701937a17fc8fa2bd8ca8e73f3" + buffer-reverse@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" @@ -2889,10 +3026,31 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" -bufio@^1.0.6: +bufio@^1.0.6, "bufio@git+https://github.com/bcoin-org/bufio.git#semver:~1.0.6", bufio@~1.0.7: version "1.0.7" resolved "git+https://github.com/bcoin-org/bufio.git#91ae6c93899ff9fad7d7cee9afd2a1c4933ca984" +"bupnp@git+https://github.com/bcoin-org/bupnp.git#semver:~0.2.6": + version "0.2.6" + resolved "git+https://github.com/bcoin-org/bupnp.git#c44fa7356aa297c9de96e8ad094a6816939cd688" + dependencies: + binet "~0.3.5" + brq "~0.1.7" + bsert "~0.0.10" + +"bval@git+https://github.com/bcoin-org/bval.git#semver:~0.1.6": + version "0.1.6" + resolved "git+https://github.com/bcoin-org/bval.git#c8cd14419ca46f63610dc48b797b076835e86f48" + dependencies: + bsert "~0.0.10" + +"bweb@git+https://github.com/bcoin-org/bweb.git#semver:=0.1.9": + version "0.1.9" + resolved "git+https://github.com/bcoin-org/bweb.git#31ae94ec9e97079610394e91928fe070d312c39d" + dependencies: + bsert "~0.0.10" + bsock "~0.1.8" + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -5568,6 +5726,10 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +"loady@git+https://github.com/chjj/loady.git#semver:~0.0.1", loady@~0.0.1, loady@~0.0.5: + version "0.0.5" + resolved "git+https://github.com/chjj/loady.git#b94958b7ee061518f4b85ea6da380e7ee93222d5" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -5986,11 +6148,19 @@ multihashes@^0.4.15, multihashes@~0.4.15: multibase "^0.7.0" varint "^5.0.0" +"n64@git+https://github.com/chjj/n64.git#semver:~0.2.10": + version "0.2.10" + resolved "git+https://github.com/chjj/n64.git#34f981f1441f569821d97a31f8cf21a3fc11b8f6" + nan@^2.13.2, nan@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +"nan@git+https://github.com/braydonf/nan.git#semver:=2.14.0": + version "2.14.0" + resolved "git+https://github.com/braydonf/nan.git#1dcc61bd06d84e389bfd5311b2b1492a14c74201" + nano-json-stream-parser@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" @@ -6765,7 +6935,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: +ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -8652,7 +8822,7 @@ wide-align@1.1.3: dependencies: string-width "^1.0.2 || 2" -wif@2.0.6, wif@^2.0.6: +wif@2.0.6, wif@^2.0.1, wif@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ= From 1b21713545df8ed7cacebbcb7cafb25b4d1f7ff3 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 9 Oct 2023 11:41:44 +0200 Subject: [PATCH 27/43] Integrate `bitcoinjs-lib` changes around deposit sweeps Here we pull changes from https://github.com/keep-network/tbtc-v2/pull/700 --- typescript/src/lib/bitcoin/address.ts | 33 +- typescript/src/lib/bitcoin/index.ts | 1 + typescript/src/lib/bitcoin/network.ts | 24 + typescript/src/lib/bitcoin/script.ts | 67 +++ typescript/src/services/deposits/deposit.ts | 55 ++- .../src/services/maintenance/wallet-tx.ts | 412 ++++++++++-------- typescript/test/bitcoin-network.test.ts | 35 +- typescript/test/bitcoin.test.ts | 86 +++- typescript/test/data/deposit-sweep.ts | 29 +- typescript/test/deposit-sweep.test.ts | 52 ++- 10 files changed, 551 insertions(+), 243 deletions(-) create mode 100644 typescript/src/lib/bitcoin/script.ts diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts index 874d995ab..cc10aebda 100644 --- a/typescript/src/lib/bitcoin/address.ts +++ b/typescript/src/lib/bitcoin/address.ts @@ -1,6 +1,36 @@ import bcoin, { Script } from "bcoin" import { Hex } from "../utils" -import { BitcoinNetwork, toBcoinNetwork } from "./network" +import { + BitcoinNetwork, + toBcoinNetwork, + toBitcoinJsLibNetwork, +} from "./network" +import { payments } from "bitcoinjs-lib" + +/** + * Creates the Bitcoin address from the public key. Supports SegWit (P2WPKH) and + * Legacy (P2PKH) formats. + * @param publicKey - Public key used to derive the Bitcoin address. + * @param bitcoinNetwork - Target Bitcoin network. + * @param witness - Flag to determine address format: true for SegWit (P2WPKH) + * and false for Legacy (P2PKH). Default is true. + * @returns The derived Bitcoin address. + */ +export function publicKeyToAddress( + publicKey: Hex, + bitcoinNetwork: BitcoinNetwork, + witness: boolean = true +): string { + const network = toBitcoinJsLibNetwork(bitcoinNetwork) + + if (witness) { + // P2WPKH (SegWit) + return payments.p2wpkh({ pubkey: publicKey.toBuffer(), network }).address! + } else { + // P2PKH (Legacy) + return payments.p2pkh({ pubkey: publicKey.toBuffer(), network }).address! + } +} /** * Converts a public key hash into a P2PKH/P2WPKH address. @@ -71,6 +101,7 @@ function outputScriptToAddress( * Utility functions allowing to perform Bitcoin address conversions. */ export const BitcoinAddressConverter = { + publicKeyToAddress, publicKeyHashToAddress, addressToPublicKeyHash, addressToOutputScript, diff --git a/typescript/src/lib/bitcoin/index.ts b/typescript/src/lib/bitcoin/index.ts index 52028bc46..5b9529bb9 100644 --- a/typescript/src/lib/bitcoin/index.ts +++ b/typescript/src/lib/bitcoin/index.ts @@ -5,5 +5,6 @@ export * from "./ecdsa-key" export * from "./hash" export * from "./header" export * from "./network" +export * from "./script" export * from "./spv" export * from "./tx" diff --git a/typescript/src/lib/bitcoin/network.ts b/typescript/src/lib/bitcoin/network.ts index bdb810e44..c40afb3bf 100644 --- a/typescript/src/lib/bitcoin/network.ts +++ b/typescript/src/lib/bitcoin/network.ts @@ -1,4 +1,5 @@ import { Hex } from "../utils" +import { networks } from "bitcoinjs-lib" /** * Bitcoin networks. @@ -64,3 +65,26 @@ export function toBcoinNetwork(bitcoinNetwork: BitcoinNetwork): string { } } } + +/** + * Converts the provided {@link BitcoinNetwork} enumeration to a format expected + * by the `bitcoinjs-lib` library. + * @param bitcoinNetwork - Specified Bitcoin network. + * @returns Network representation compatible with the `bitcoinjs-lib` library. + * @throws An error if the network is not supported by `bitcoinjs-lib`. + */ +export function toBitcoinJsLibNetwork( + bitcoinNetwork: BitcoinNetwork +): networks.Network { + switch (bitcoinNetwork) { + case BitcoinNetwork.Mainnet: { + return networks.bitcoin + } + case BitcoinNetwork.Testnet: { + return networks.testnet + } + default: { + throw new Error(`network not supported`) + } + } +} diff --git a/typescript/src/lib/bitcoin/script.ts b/typescript/src/lib/bitcoin/script.ts new file mode 100644 index 000000000..a66c39305 --- /dev/null +++ b/typescript/src/lib/bitcoin/script.ts @@ -0,0 +1,67 @@ +import { payments } from "bitcoinjs-lib" + +/** + * Checks if the provided script comes from a P2PKH input. + * @param script The script to be checked. + * @returns True if the script is P2PKH, false otherwise. + */ +function isP2PKHScript(script: Buffer): boolean { + try { + payments.p2pkh({ output: script }) + return true + } catch (err) { + return false + } +} + +/** + * Checks if the provided script comes from a P2WPKH input. + * @param script The script to be checked. + * @returns True if the script is P2WPKH, false otherwise. + */ +function isP2WPKHScript(script: Buffer): boolean { + try { + payments.p2wpkh({ output: script }) + return true + } catch (err) { + return false + } +} + +/** + * Checks if the provided script comes from a P2SH input. + * @param script The script to be checked. + * @returns True if the script is P2SH, false otherwise. + */ +function isP2SHScript(script: Buffer): boolean { + try { + payments.p2sh({ output: script }) + return true + } catch (err) { + return false + } +} + +/** + * Checks if the provided script comes from a P2PKH input. + * @param script The script to be checked. + * @returns True if the script is P2WSH, false otherwise. + */ +function isP2WSHScript(script: Buffer): boolean { + try { + payments.p2wsh({ output: script }) + return true + } catch (err) { + return false + } +} + +/** + * Utility functions allowing to deal with Bitcoin scripts. + */ +export const BitcoinScriptUtils = { + isP2PKHScript, + isP2WPKHScript, + isP2SHScript, + isP2WSHScript, +} diff --git a/typescript/src/services/deposits/deposit.ts b/typescript/src/services/deposits/deposit.ts index 4ce3099a3..19e36e9e0 100644 --- a/typescript/src/services/deposits/deposit.ts +++ b/typescript/src/services/deposits/deposit.ts @@ -12,8 +12,7 @@ import { extractBitcoinRawTxVectors, toBcoinNetwork, } from "../../lib/bitcoin" - -const { opcodes } = bcoin.script.common +import { Stack, script, opcodes } from "bitcoinjs-lib" /** * Component representing an instance of the tBTC v2 deposit process. @@ -193,33 +192,31 @@ export class DepositScript { * @returns Plain-text deposit script as an un-prefixed hex string. */ async getPlainText(): Promise { - // All HEXes pushed to the script must be un-prefixed. - const script = new bcoin.Script() - script.clear() - script.pushData(Buffer.from(this.receipt.depositor.identifierHex, "hex")) - script.pushOp(opcodes.OP_DROP) - script.pushData(Buffer.from(this.receipt.blindingFactor, "hex")) - script.pushOp(opcodes.OP_DROP) - script.pushOp(opcodes.OP_DUP) - script.pushOp(opcodes.OP_HASH160) - script.pushData(Buffer.from(this.receipt.walletPublicKeyHash, "hex")) - script.pushOp(opcodes.OP_EQUAL) - script.pushOp(opcodes.OP_IF) - script.pushOp(opcodes.OP_CHECKSIG) - script.pushOp(opcodes.OP_ELSE) - script.pushOp(opcodes.OP_DUP) - script.pushOp(opcodes.OP_HASH160) - script.pushData(Buffer.from(this.receipt.refundPublicKeyHash, "hex")) - script.pushOp(opcodes.OP_EQUALVERIFY) - script.pushData(Buffer.from(this.receipt.refundLocktime, "hex")) - script.pushOp(opcodes.OP_CHECKLOCKTIMEVERIFY) - script.pushOp(opcodes.OP_DROP) - script.pushOp(opcodes.OP_CHECKSIG) - script.pushOp(opcodes.OP_ENDIF) - script.compile() - - // Return script as HEX string. - return script.toRaw().toString("hex") + const chunks: Stack = [] + + // All HEXes pushed to the script must be un-prefixed + chunks.push(Buffer.from(this.receipt.depositor.identifierHex, "hex")) + chunks.push(opcodes.OP_DROP) + chunks.push(Buffer.from(this.receipt.blindingFactor, "hex")) + chunks.push(opcodes.OP_DROP) + chunks.push(opcodes.OP_DUP) + chunks.push(opcodes.OP_HASH160) + chunks.push(Buffer.from(this.receipt.walletPublicKeyHash, "hex")) + chunks.push(opcodes.OP_EQUAL) + chunks.push(opcodes.OP_IF) + chunks.push(opcodes.OP_CHECKSIG) + chunks.push(opcodes.OP_ELSE) + chunks.push(opcodes.OP_DUP) + chunks.push(opcodes.OP_HASH160) + chunks.push(Buffer.from(this.receipt.refundPublicKeyHash, "hex")) + chunks.push(opcodes.OP_EQUALVERIFY) + chunks.push(Buffer.from(this.receipt.refundLocktime, "hex")) + chunks.push(opcodes.OP_CHECKLOCKTIMEVERIFY) + chunks.push(opcodes.OP_DROP) + chunks.push(opcodes.OP_CHECKSIG) + chunks.push(opcodes.OP_ENDIF) + + return script.compile(chunks).toString("hex") } /** diff --git a/typescript/src/services/maintenance/wallet-tx.ts b/typescript/src/services/maintenance/wallet-tx.ts index 970f50574..4c017bebb 100644 --- a/typescript/src/services/maintenance/wallet-tx.ts +++ b/typescript/src/services/maintenance/wallet-tx.ts @@ -1,11 +1,15 @@ import { + BitcoinAddressConverter, BitcoinClient, BitcoinHashUtils, + BitcoinNetwork, BitcoinPrivateKeyUtils, BitcoinPublicKeyUtils, BitcoinRawTx, + BitcoinScriptUtils, BitcoinTxHash, BitcoinUtxo, + toBitcoinJsLibNetwork, } from "../../lib/bitcoin" import { BigNumber } from "ethers" import { @@ -15,6 +19,17 @@ import { } from "../../lib/contracts" import bcoin from "bcoin" import { DepositScript } from "../deposits" +import { ECPairFactory } from "ecpair" +import * as tinysecp from "tiny-secp256k1" +import { Hex } from "../../lib/utils" +import { + payments, + script, + Signer, + Stack, + Transaction, + TxOutput, +} from "bitcoinjs-lib" /** * Wallet transactions builder. This feature set is supposed to be used only @@ -111,8 +126,11 @@ class DepositSweep { } } + const bitcoinNetwork = await this.bitcoinClient.getNetwork() + const { transactionHash, newMainUtxo, rawTransaction } = await this.assembleTransaction( + bitcoinNetwork, fee, walletPrivateKey, utxosWithRaw, @@ -132,7 +150,8 @@ class DepositSweep { * Assembles a Bitcoin P2WPKH deposit sweep transaction. * @dev The caller is responsible for ensuring the provided UTXOs are correctly * formed, can be spent by the wallet and their combined value is greater - * then the fee. + * than the fee. + * @param bitcoinNetwork - The target Bitcoin network. * @param fee - the value that should be subtracted from the sum of the UTXOs * values and used as the transaction fee. * @param walletPrivateKey - Bitcoin private key of the wallet in WIF format. @@ -145,8 +164,11 @@ class DepositSweep { * - the sweep transaction hash, * - the new wallet's main UTXO produced by this transaction. * - the sweep transaction in the raw format + * @throws Error if the provided UTXOs and deposits mismatch or if an unsupported + * UTXO script type is encountered. */ async assembleTransaction( + bitcoinNetwork: BitcoinNetwork, fee: BigNumber, walletPrivateKey: string, utxos: (BitcoinUtxo & BitcoinRawTx)[], @@ -167,252 +189,289 @@ class DepositSweep { ) } - const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( + const network = toBitcoinJsLibNetwork(bitcoinNetwork) + // eslint-disable-next-line new-cap + const walletKeyPair = ECPairFactory(tinysecp).fromWIF( walletPrivateKey, + network + ) + const walletAddress = BitcoinAddressConverter.publicKeyToAddress( + Hex.from(walletKeyPair.publicKey), + bitcoinNetwork, this.witness ) - const walletAddress = walletKeyRing.getAddress("string") - const inputCoins = [] - let totalInputValue = BigNumber.from(0) + const transaction = new Transaction() + let outputValue = BigNumber.from(0) if (mainUtxo) { - inputCoins.push( - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), - mainUtxo.outputIndex, - -1 - ) + transaction.addInput( + mainUtxo.transactionHash.reverse().toBuffer(), + mainUtxo.outputIndex ) - totalInputValue = totalInputValue.add(mainUtxo.value) + outputValue = outputValue.add(mainUtxo.value) } - for (const utxo of utxos) { - inputCoins.push( - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), - utxo.outputIndex, - -1 - ) + transaction.addInput( + utxo.transactionHash.reverse().toBuffer(), + utxo.outputIndex ) - totalInputValue = totalInputValue.add(utxo.value) + outputValue = outputValue.add(utxo.value) } + outputValue = outputValue.sub(fee) - const transaction = new bcoin.MTX() + const outputScript = + BitcoinAddressConverter.addressToOutputScript(walletAddress) - transaction.addOutput({ - script: bcoin.Script.fromAddress(walletAddress), - value: totalInputValue.toNumber(), - }) + transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber()) - await transaction.fund(inputCoins, { - changeAddress: walletAddress, - hardFee: fee.toNumber(), - subtractFee: true, - }) + // Sign the main UTXO input if there is main UTXO. + if (mainUtxo) { + const inputIndex = 0 // Main UTXO is the first input. + const previousOutput = Transaction.fromHex(mainUtxo.transactionHex).outs[ + mainUtxo.outputIndex + ] - if (transaction.outputs.length != 1) { - throw new Error("Deposit sweep transaction must have only one output") + await this.signMainUtxoInput( + transaction, + inputIndex, + previousOutput, + walletKeyPair + ) } - // UTXOs must be mapped to deposits, as `fund` may arrange inputs in any - // order - const utxosWithDeposits: (BitcoinUtxo & BitcoinRawTx & DepositReceipt)[] = - utxos.map((utxo, index) => ({ - ...utxo, - ...deposits[index], - })) - - for (let i = 0; i < transaction.inputs.length; i++) { - const previousOutpoint = transaction.inputs[i].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - const previousScript = previousOutput.script - - // P2(W)PKH (main UTXO) - if ( - previousScript.isPubkeyhash() || - previousScript.isWitnessPubkeyhash() - ) { - await this.signMainUtxoInput(transaction, i, walletKeyRing) - continue - } + // Sign the deposit inputs. + for (let depositIndex = 0; depositIndex < deposits.length; depositIndex++) { + // If there is a main UTXO index, we must adjust input index as the first + // input is the main UTXO input. + const inputIndex = mainUtxo ? depositIndex + 1 : depositIndex - const utxoWithDeposit = utxosWithDeposits.find( - (u) => - u.transactionHash.toString() === previousOutpoint.txid() && - u.outputIndex == previousOutpoint.index - ) - if (!utxoWithDeposit) { - throw new Error("Unknown input") - } + const utxo = utxos[depositIndex] + const previousOutput = Transaction.fromHex(utxo.transactionHex).outs[ + utxo.outputIndex + ] + const previousOutputValue = previousOutput.value + const previousOutputScript = previousOutput.script - if (previousScript.isScripthash()) { + const deposit = deposits[depositIndex] + + if (BitcoinScriptUtils.isP2SHScript(previousOutputScript)) { // P2SH (deposit UTXO) await this.signP2SHDepositInput( transaction, - i, - utxoWithDeposit, - walletKeyRing + inputIndex, + deposit, + previousOutputValue, + walletKeyPair ) - } else if (previousScript.isWitnessScripthash()) { + } else if (BitcoinScriptUtils.isP2WSHScript(previousOutputScript)) { // P2WSH (deposit UTXO) await this.signP2WSHDepositInput( transaction, - i, - utxoWithDeposit, - walletKeyRing + inputIndex, + deposit, + previousOutputValue, + walletKeyPair ) } else { throw new Error("Unsupported UTXO script type") } } - const transactionHash = BitcoinTxHash.from(transaction.txid()) + const transactionHash = BitcoinTxHash.from(transaction.getId()) return { transactionHash, newMainUtxo: { transactionHash, outputIndex: 0, // There is only one output. - value: BigNumber.from(transaction.outputs[0].value), + value: BigNumber.from(transaction.outs[0].value), }, rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), + transactionHex: transaction.toHex(), }, } } /** - * Creates script for the transaction input at the given index and signs the - * input. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Empty promise. + * Signs the main UTXO transaction input and sets the appropriate script or + * witness data. + * @param transaction - The transaction containing the input to be signed. + * @param inputIndex - Index pointing to the input within the transaction. + * @param previousOutput - The previous output for the main UTXO input. + * @param walletKeyPair - A Signer object with the wallet's public and private + * key pair. + * @returns An empty promise upon successful signing. + * @throws Error if the UTXO doesn't belong to the wallet, or if the script + * format is invalid or unknown. */ private async signMainUtxoInput( - transaction: any, + transaction: Transaction, inputIndex: number, - walletKeyRing: any + previousOutput: TxOutput, + walletKeyPair: Signer ) { - const previousOutpoint = transaction.inputs[inputIndex].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - if (!walletKeyRing.ownOutput(previousOutput)) { + if ( + !this.canSpendOutput( + Hex.from(walletKeyPair.publicKey), + previousOutput.script + ) + ) { throw new Error("UTXO does not belong to the wallet") } - // Build script and set it as input's witness - transaction.scriptInput(inputIndex, previousOutput, walletKeyRing) - // Build signature and add it in front of script in input's witness - transaction.signInput(inputIndex, previousOutput, walletKeyRing) + + const sigHashType = Transaction.SIGHASH_ALL + + if (BitcoinScriptUtils.isP2PKHScript(previousOutput.script)) { + // P2PKH + const sigHash = transaction.hashForSignature( + inputIndex, + previousOutput.script, + sigHashType + ) + + const signature = script.signature.encode( + walletKeyPair.sign(sigHash), + sigHashType + ) + + const scriptSig = payments.p2pkh({ + signature: signature, + pubkey: walletKeyPair.publicKey, + }).input! + + transaction.ins[inputIndex].script = scriptSig + } else if (BitcoinScriptUtils.isP2WPKHScript(previousOutput.script)) { + // P2WPKH + const publicKeyHash = payments.p2wpkh({ output: previousOutput.script }) + .hash! + const p2pkhScript = payments.p2pkh({ hash: publicKeyHash }).output! + + const sigHash = transaction.hashForWitnessV0( + inputIndex, + p2pkhScript, + previousOutput.value, + sigHashType + ) + + const signature = script.signature.encode( + walletKeyPair.sign(sigHash), + sigHashType + ) + + transaction.ins[inputIndex].witness = [signature, walletKeyPair.publicKey] + } else { + throw new Error("Unknown type of main UTXO") + } } /** - * Creates and sets `scriptSig` for the transaction input at the given index by - * combining signature, wallet public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param deposit - Data of the deposit. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Empty promise. + * Signs a P2SH deposit transaction input and sets the `scriptSig`. + * @param transaction - The transaction containing the input to be signed. + * @param inputIndex - Index pointing to the input within the transaction. + * @param deposit - Details of the deposit transaction. + * @param previousOutputValue - The value from the previous transaction output. + * @param walletKeyPair - A Signer object with the wallet's public and private + * key pair. + * @returns An empty promise upon successful signing. */ private async signP2SHDepositInput( - transaction: any, + transaction: Transaction, inputIndex: number, deposit: DepositReceipt, - walletKeyRing: any + previousOutputValue: number, + walletKeyPair: Signer ): Promise { - const { walletPublicKey, depositScript, previousOutputValue } = - await this.prepareInputSignData( - transaction, - inputIndex, - deposit, - walletKeyRing - ) + const depositScript = await this.prepareDepositScript( + deposit, + previousOutputValue, + walletKeyPair + ) + + const sigHashType = Transaction.SIGHASH_ALL - const signature: Buffer = transaction.signature( + const sigHash = transaction.hashForSignature( inputIndex, depositScript, - previousOutputValue, - walletKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 0 // legacy sighash version + sigHashType ) - const scriptSig = new bcoin.Script() - scriptSig.clear() - scriptSig.pushData(signature) - scriptSig.pushData(Buffer.from(walletPublicKey, "hex")) - scriptSig.pushData(depositScript.toRaw()) - scriptSig.compile() - - transaction.inputs[inputIndex].script = scriptSig + + const signature = script.signature.encode( + walletKeyPair.sign(sigHash), + sigHashType + ) + + const scriptSig: Stack = [] + scriptSig.push(signature) + scriptSig.push(walletKeyPair.publicKey) + scriptSig.push(depositScript) + + transaction.ins[inputIndex].script = script.compile(scriptSig) } /** - * Creates and sets witness script for the transaction input at the given index - * by combining signature, wallet public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param deposit - Data of the deposit. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Empty promise. + * Signs a P2WSH deposit transaction input and sets the witness script. + * @param transaction - The transaction containing the input to be signed. + * @param inputIndex - Index pointing to the input within the transaction. + * @param deposit - Details of the deposit transaction. + * @param previousOutputValue - The value from the previous transaction output. + * @param walletKeyPair - A Signer object with the wallet's public and private + * key pair. + * @returns An empty promise upon successful signing. */ private async signP2WSHDepositInput( - transaction: any, + transaction: Transaction, inputIndex: number, deposit: DepositReceipt, - walletKeyRing: any + previousOutputValue: number, + walletKeyPair: Signer ): Promise { - const { walletPublicKey, depositScript, previousOutputValue } = - await this.prepareInputSignData( - transaction, - inputIndex, - deposit, - walletKeyRing - ) + const depositScript = await this.prepareDepositScript( + deposit, + previousOutputValue, + walletKeyPair + ) + + const sigHashType = Transaction.SIGHASH_ALL - const signature: Buffer = transaction.signature( + const sigHash = transaction.hashForWitnessV0( inputIndex, depositScript, previousOutputValue, - walletKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 1 // segwit sighash version + sigHashType ) - const witness = new bcoin.Witness() - witness.clear() - witness.pushData(signature) - witness.pushData(Buffer.from(walletPublicKey, "hex")) - witness.pushData(depositScript.toRaw()) - witness.compile() + const signature = script.signature.encode( + walletKeyPair.sign(sigHash), + sigHashType + ) + + const witness: Buffer[] = [] + witness.push(signature) + witness.push(walletKeyPair.publicKey) + witness.push(depositScript) - transaction.inputs[inputIndex].witness = witness + transaction.ins[inputIndex].witness = witness } /** - * Creates data needed to sign a deposit input. - * @param transaction - Mutable transaction containing the input. - * @param inputIndex - Index that points to the input. - * @param deposit - Data of the deposit. - * @param walletKeyRing - Key ring created using the wallet's private key. - * @returns Data needed to sign the input. + * Assembles the deposit script based on the given deposit details. Performs + * validations on values and key formats. + * @param deposit - The deposit details. + * @param previousOutputValue - Value from the previous transaction output. + * @param walletKeyPair - Signer object containing the wallet's key pair. + * @returns A Promise resolving to the assembled deposit script as a Buffer. + * @throws Error if there are discrepancies in values or key formats. */ - private async prepareInputSignData( - transaction: any, - inputIndex: number, + private async prepareDepositScript( deposit: DepositReceipt, - walletKeyRing: any - ): Promise<{ - walletPublicKey: string - depositScript: any - previousOutputValue: number - }> { - const previousOutpoint = transaction.inputs[inputIndex].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) + previousOutputValue: number, + walletKeyPair: Signer + ): Promise { + const walletPublicKey = walletKeyPair.publicKey.toString("hex") - const walletPublicKey = walletKeyRing.getPublicKey("hex") if ( - BitcoinHashUtils.computeHash160(walletKeyRing.getPublicKey("hex")) != + BitcoinHashUtils.computeHash160(walletPublicKey) != deposit.walletPublicKeyHash ) { throw new Error( @@ -424,18 +483,27 @@ class DepositSweep { throw new Error("Wallet public key must be compressed") } - const depositScript = bcoin.Script.fromRaw( - Buffer.from( - await DepositScript.fromReceipt(deposit).getPlainText(), - "hex" - ) + return Buffer.from( + await DepositScript.fromReceipt(deposit).getPlainText(), + "hex" ) + } - return { - walletPublicKey, - depositScript: depositScript, - previousOutputValue: previousOutput.value, - } + /** + * Determines if a UTXO's output script can be spent using the provided public + * key. + * @param publicKey - Public key used to derive the corresponding P2PKH and + * P2WPKH output scripts. + * @param outputScript - The output script of the UTXO in question. + * @returns True if the provided output script matches the P2PKH or P2WPKH + * output scripts derived from the given public key. False otherwise. + */ + private canSpendOutput(publicKey: Hex, outputScript: Buffer): boolean { + const pubkeyBuffer = publicKey.toBuffer() + const p2pkhOutput = payments.p2pkh({ pubkey: pubkeyBuffer }).output! + const p2wpkhOutput = payments.p2wpkh({ pubkey: pubkeyBuffer }).output! + + return outputScript.equals(p2pkhOutput) || outputScript.equals(p2wpkhOutput) } } diff --git a/typescript/test/bitcoin-network.test.ts b/typescript/test/bitcoin-network.test.ts index db4555b74..32102f996 100644 --- a/typescript/test/bitcoin-network.test.ts +++ b/typescript/test/bitcoin-network.test.ts @@ -1,5 +1,11 @@ import { expect } from "chai" -import { BitcoinTxHash, BitcoinNetwork, toBcoinNetwork } from "../src" +import { + BitcoinTxHash, + BitcoinNetwork, + toBcoinNetwork, + toBitcoinJsLibNetwork, +} from "../src" +import { networks } from "bitcoinjs-lib" describe("BitcoinNetwork", () => { const testData = [ @@ -9,6 +15,7 @@ describe("BitcoinNetwork", () => { // any value that doesn't match other supported networks genesisHash: BitcoinTxHash.from("0x00010203"), expectedToBcoinResult: new Error("network not supported"), + expectedToBitcoinJsLibResult: new Error("network not supported"), }, { enumKey: BitcoinNetwork.Testnet, @@ -17,6 +24,7 @@ describe("BitcoinNetwork", () => { "0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" ), expectedToBcoinResult: "testnet", + expectedToBitcoinJsLibResult: networks.testnet, }, { enumKey: BitcoinNetwork.Mainnet, @@ -25,11 +33,18 @@ describe("BitcoinNetwork", () => { "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" ), expectedToBcoinResult: "main", + expectedToBitcoinJsLibResult: networks.bitcoin, }, ] testData.forEach( - ({ enumKey, enumValue, genesisHash, expectedToBcoinResult }) => { + ({ + enumKey, + enumValue, + genesisHash, + expectedToBcoinResult, + expectedToBitcoinJsLibResult, + }) => { context(enumKey, async () => { describe(`toString`, async () => { it(`should return correct value`, async () => { @@ -58,6 +73,22 @@ describe("BitcoinNetwork", () => { }) } }) + + describe(`toBitcoinJsLibNetwork`, async () => { + if (expectedToBitcoinJsLibResult instanceof Error) { + it(`should throw an error`, async () => { + expect(() => toBitcoinJsLibNetwork(enumKey)).to.throw( + expectedToBitcoinJsLibResult.message + ) + }) + } else { + it(`should return ${expectedToBitcoinJsLibResult}`, async () => { + expect(toBitcoinJsLibNetwork(enumKey)).to.be.equal( + expectedToBitcoinJsLibResult + ) + }) + } + }) }) } ) diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index 785875480..c695fbee4 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -10,9 +10,10 @@ import { BitcoinCompactSizeUint, BitcoinAddressConverter, Hex, + BitcoinScriptUtils, } from "../src" import { BigNumber } from "ethers" -import { btcAddresses } from "./data/bitcoin" +import { btcAddresses, btcAddressFromPublicKey } from "./data/bitcoin" describe("Bitcoin", () => { describe("BitcoinPublicKeyUtils", () => { @@ -113,12 +114,37 @@ describe("Bitcoin", () => { const P2PKHAddressTestnet = "mkpoZkRvtd3SDGWgUDuXK1aEXZfHRM2gKw" const { + publicKeyToAddress, publicKeyHashToAddress, addressToPublicKeyHash, addressToOutputScript, outputScriptToAddress, } = BitcoinAddressConverter + describe("publicKeyToAddress", () => { + Object.entries(btcAddressFromPublicKey).forEach( + ([bitcoinNetwork, addressData]) => { + context(`with ${bitcoinNetwork} addresses`, () => { + Object.entries(addressData).forEach( + ([addressType, { publicKey, address }]) => { + it(`should return correct ${addressType} address for ${bitcoinNetwork}`, () => { + const witness = addressType === "P2WPKH" + const result = publicKeyToAddress( + publicKey, + bitcoinNetwork === "mainnet" + ? BitcoinNetwork.Mainnet + : BitcoinNetwork.Testnet, + witness + ) + expect(result).to.eq(address) + }) + } + ) + }) + } + ) + }) + describe("publicKeyHashToAddress", () => { context("when network is mainnet", () => { context("when witness option is true", () => { @@ -612,4 +638,62 @@ describe("Bitcoin", () => { }) }) }) + + describe("BitcoinScriptUtils", () => { + const { isP2PKHScript, isP2WPKHScript, isP2SHScript, isP2WSHScript } = + BitcoinScriptUtils + + describe("isScript", () => { + const testData = [ + { + testFunction: isP2PKHScript, + validScript: Buffer.from( + "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac", + "hex" + ), + name: "P2PKH", + }, + { + testFunction: isP2WPKHScript, + validScript: Buffer.from( + "00148db50eb52063ea9d98b3eac91489a90f738986f6", + "hex" + ), + name: "P2WPKH", + }, + { + testFunction: isP2SHScript, + validScript: Buffer.from( + "a914a9a5f97d5d3c4687a52e90718168270005b369c487", + "hex" + ), + name: "P2SH", + }, + { + testFunction: isP2WSHScript, + validScript: Buffer.from( + "0020b1f83e226979dc9fe74e87f6d303dbb08a27a1c7ce91664033f34c7f2d214cd7", + "hex" + ), + name: "P2WSH", + }, + ] + + testData.forEach(({ testFunction, validScript, name }) => { + describe(`is${name}Script`, () => { + it(`should return true for a valid ${name} script`, () => { + expect(testFunction(validScript)).to.be.true + }) + + it("should return false for other scripts", () => { + testData.forEach((data) => { + if (data.name !== name) { + expect(testFunction(data.validScript)).to.be.false + } + }) + }) + }) + }) + }) + }) }) diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index 0c962b841..0ba6ffee6 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -374,24 +374,23 @@ export const depositSweepWithNonWitnessMainUtxoAndWitnessOutput: DepositSweepTes witness: true, expectedSweep: { transactionHash: BitcoinTxHash.from( - "7831d0dfde7e160f3b9bb66c433710f0d3110d73ea78b9db65e81c091a6718a0" + "1933781f01f27f086c3a31c4a53035ebc7c4688e1f4b316babefa8f6dab77dc2" ), transaction: { transactionHex: - "01000000000102173a201f597a2c8ccd7842303a6653bb87437fb08dae671731a0" + - "75403b32a2fd0000000000ffffffffe19612be756bf7e740b47bec0e24845089ac" + - "e48c78d473cb34949b3007c4a2c8000000006a47304402204382deb051f9f3e2b5" + - "39e4bac2d1a50faf8d66bc7a3a3f3d286dabd96d92b58b02207c74c6aaf48e25d0" + - "7e02bb4039606d77ecfd80c492c050ab2486af6027fc2d5a012103989d253b17a6" + - "a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d9ffffffff010884" + - "0000000000001600148db50eb52063ea9d98b3eac91489a90f738986f603483045" + - "022100c52bc876cdee80a3061ace3ffbce5e860942d444cd38e00e5f63fd8e818d" + - "7e7c022040a7017bb8213991697705e7092c481526c788a4731d06e582dc1c57be" + - "d7243b012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc2" + - "9dcf8581d95c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d" + - "000395237576a9148db50eb52063ea9d98b3eac91489a90f738986f68763ac6776" + - "a914e257eccafbc07c381642ce6e7e55120fb077fbed880448f2b262b175ac6800" + - "00000000", + "01000000000102e19612be756bf7e740b47bec0e24845089ace48c78d473cb34949" + + "b3007c4a2c8000000006a473044022013787be70eda0620002fa55c92abfcd32257" + + "d64fa652dd32bac65d705162a95902203407fea1abc99a9273ead3179ce60f60a34" + + "33fb2e93f58569e4bee9f63c0d679012103989d253b17a6a0f41838b84ff0d20e88" + + "98f9d7b1a98f2564da4cc29dcf8581d9ffffffff173a201f597a2c8ccd7842303a6" + + "653bb87437fb08dae671731a075403b32a2fd0000000000ffffffff010884000000" + + "0000001600148db50eb52063ea9d98b3eac91489a90f738986f6000348304502210" + + "0804f0fa989d632cda99a24159e28b8d31d4033c2d5de47d8207ea2767273d10a02" + + "20278e82d0714867b31eb013762306e2b97c2c1cc74b8135bee78d565e72ee630e0" + + "12103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581" + + "d95c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d000395237" + + "576a9148db50eb52063ea9d98b3eac91489a90f738986f68763ac6776a914e257ec" + + "cafbc07c381642ce6e7e55120fb077fbed880448f2b262b175ac6800000000", }, }, } diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts index 403d56020..10aa2b640 100644 --- a/typescript/test/deposit-sweep.test.ts +++ b/typescript/test/deposit-sweep.test.ts @@ -1,5 +1,6 @@ import { BigNumber } from "ethers" import { + BitcoinNetwork, BitcoinRawTx, BitcoinTxHash, BitcoinUtxo, @@ -39,8 +40,6 @@ describe("Sweep", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { - bcoin.set("testnet") - tbtcContracts = new MockTBTCContracts() bitcoinClient = new MockBitcoinClient() }) @@ -430,6 +429,7 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, utxosWithRaw, @@ -570,6 +570,7 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, utxosWithRaw, @@ -726,6 +727,7 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, utxosWithRaw, @@ -753,25 +755,7 @@ describe("Sweep", () => { // Validate inputs. expect(txJSON.inputs.length).to.be.equal(2) - const p2wshInput = txJSON.inputs[0] - expect(p2wshInput.prevout.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2wshInput.prevout.index).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .deposits[0].utxo.outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2wshInput.address).to.be.equal( - "tb1qk8urugnf08wfle6wslmdxq7mkz9z0gw8e6gkvspn7dx87tfpfntshdm7qr" - ) - - const p2pkhInput = txJSON.inputs[1] // main UTXO + const p2pkhInput = txJSON.inputs[0] // main UTXO expect(p2pkhInput.prevout.hash).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() ) @@ -789,6 +773,24 @@ describe("Sweep", () => { "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" ) + const p2wshInput = txJSON.inputs[1] + expect(p2wshInput.prevout.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2wshInput.prevout.index).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .deposits[0].utxo.outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + // Input's address should be set to the address generated from deposit + // script hash + expect(p2wshInput.address).to.be.equal( + "tb1qk8urugnf08wfle6wslmdxq7mkz9z0gw8e6gkvspn7dx87tfpfntshdm7qr" + ) + // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -860,6 +862,7 @@ describe("Sweep", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, utxosWithRaw, @@ -948,6 +951,7 @@ describe("Sweep", () => { await expect( walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, [], @@ -985,6 +989,7 @@ describe("Sweep", () => { await expect( walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, utxosWithRaw, @@ -1025,6 +1030,7 @@ describe("Sweep", () => { await expect( walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, [utxoWithRaw], @@ -1050,6 +1056,7 @@ describe("Sweep", () => { await expect( walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, anotherPrivateKey, [utxoWithRaw], @@ -1084,6 +1091,7 @@ describe("Sweep", () => { await expect( walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, fee, testnetWalletPrivateKey, [utxoWithRaw], @@ -1103,8 +1111,6 @@ describe("Sweep", () => { let maintenanceService: MaintenanceService beforeEach(async () => { - bcoin.set("testnet") - bitcoinClient = new MockBitcoinClient() tbtcContracts = new MockTBTCContracts() From 6647d69fc42a897f0aaffc3cadf56c0ba4ead576 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 10 Oct 2023 11:00:23 +0200 Subject: [PATCH 28/43] Integrate `bitcoinjs-lib` changes around deposits Here we pull changes from https://github.com/keep-network/tbtc-v2/pull/702 --- typescript/src/lib/bitcoin/hash.ts | 13 ++ typescript/src/services/deposits/deposit.ts | 42 ++++-- typescript/src/services/deposits/funding.ts | 144 +++++++++++++++----- typescript/test/bitcoin.test.ts | 17 ++- typescript/test/deposit.test.ts | 38 ++++-- 5 files changed, 193 insertions(+), 61 deletions(-) diff --git a/typescript/src/lib/bitcoin/hash.ts b/typescript/src/lib/bitcoin/hash.ts index 31426edf9..018771f19 100644 --- a/typescript/src/lib/bitcoin/hash.ts +++ b/typescript/src/lib/bitcoin/hash.ts @@ -19,6 +19,7 @@ function computeHash160(text: string): string { * Computes the double SHA256 for the given text. * @param text - Text the double SHA256 is computed for. * @returns Hash as a 32-byte un-prefixed hex string. + * @dev Do not confuse it with computeSha256 which computes single SHA256. */ function computeHash256(text: Hex): Hex { const firstHash = utils.sha256(text.toPrefixedString()) @@ -36,6 +37,17 @@ function hashLEToBigNumber(hash: Hex): BigNumber { return BigNumber.from(hash.reverse().toPrefixedString()) } +/** + * Computes the single SHA256 for the given text. + * @param text - Text the single SHA256 is computed for. + * @returns Hash as a 32-byte un-prefixed hex string. + * @dev Do not confuse it with computeHash256 which computes double SHA256. + */ +function computeSha256(text: Hex): Hex { + const hash = utils.sha256(text.toPrefixedString()) + return Hex.from(hash) +} + /** * Utility functions allowing to deal with Bitcoin hashes. */ @@ -43,4 +55,5 @@ export const BitcoinHashUtils = { computeHash160, computeHash256, hashLEToBigNumber, + computeSha256, } diff --git a/typescript/src/services/deposits/deposit.ts b/typescript/src/services/deposits/deposit.ts index 19e36e9e0..ed93eff73 100644 --- a/typescript/src/services/deposits/deposit.ts +++ b/typescript/src/services/deposits/deposit.ts @@ -3,16 +3,17 @@ import { TBTCContracts, validateDepositReceipt, } from "../../lib/contracts" -import bcoin from "bcoin" import { BitcoinClient, + BitcoinHashUtils, BitcoinNetwork, BitcoinTxOutpoint, BitcoinUtxo, extractBitcoinRawTxVectors, - toBcoinNetwork, + toBitcoinJsLibNetwork, } from "../../lib/bitcoin" -import { Stack, script, opcodes } from "bitcoinjs-lib" +import { payments, Stack, script, opcodes } from "bitcoinjs-lib" +import { Hex } from "../../lib/utils" /** * Component representing an instance of the tBTC v2 deposit process. @@ -181,11 +182,11 @@ export class DepositScript { */ async getHash(): Promise { const script = await this.getPlainText() - // Parse the script from HEX string. - const parsedScript = bcoin.Script.fromRaw(Buffer.from(script, "hex")) // If witness script hash should be produced, SHA256 should be used. // Legacy script hash needs HASH160. - return this.witness ? parsedScript.sha256() : parsedScript.hash160() + return this.witness + ? BitcoinHashUtils.computeSha256(Hex.from(script)).toBuffer() + : Buffer.from(BitcoinHashUtils.computeHash160(script), "hex") } /** @@ -226,9 +227,30 @@ export class DepositScript { */ async deriveAddress(bitcoinNetwork: BitcoinNetwork): Promise { const scriptHash = await this.getHash() - const address = this.witness - ? bcoin.Address.fromWitnessScripthash(scriptHash) - : bcoin.Address.fromScripthash(scriptHash) - return address.toString(toBcoinNetwork(bitcoinNetwork)) + + const bitcoinJsLibNetwork = toBitcoinJsLibNetwork(bitcoinNetwork) + + if (this.witness) { + // OP_0 + const p2wshOutput = Buffer.concat([ + Buffer.from([opcodes.OP_0, 0x20]), + scriptHash, + ]) + + return payments.p2wsh({ + output: p2wshOutput, + network: bitcoinJsLibNetwork, + }).address! + } else { + // OP_HASH160 OP_EQUAL + const p2shOutput = Buffer.concat([ + Buffer.from([opcodes.OP_HASH160, 0x14]), + scriptHash, + Buffer.from([opcodes.OP_EQUAL]), + ]) + + return payments.p2sh({ output: p2shOutput, network: bitcoinJsLibNetwork }) + .address! + } } } diff --git a/typescript/src/services/deposits/funding.ts b/typescript/src/services/deposits/funding.ts index a13cc86f5..ff250d096 100644 --- a/typescript/src/services/deposits/funding.ts +++ b/typescript/src/services/deposits/funding.ts @@ -1,13 +1,19 @@ import { DepositScript } from "./deposit" import { + BitcoinAddressConverter, BitcoinClient, - BitcoinPrivateKeyUtils, + BitcoinNetwork, BitcoinRawTx, + BitcoinScriptUtils, BitcoinTxHash, BitcoinUtxo, + toBitcoinJsLibNetwork, } from "../../lib/bitcoin" import { BigNumber } from "ethers" -import bcoin from "bcoin" +import { Psbt, Transaction } from "bitcoinjs-lib" +import { ECPairFactory } from "ecpair" +import * as tinysecp from "tiny-secp256k1" +import { Hex } from "../../lib/utils" /** * Component allowing to craft and submit the Bitcoin funding transaction using @@ -37,56 +43,104 @@ export class DepositFunding { * can be unlocked using the depositor's private key. It is also * caller's responsibility to ensure the given deposit is funded exactly * once. + * @param bitcoinNetwork The target Bitcoin network. * @param amount Deposit amount in satoshis. - * @param inputUtxos UTXOs that should be used as transaction inputs. + * @param inputUtxos UTXOs to be used for funding the deposit transaction. + * So far only P2WPKH UTXO inputs are supported. + * @param fee Transaction fee to be subtracted from the sum of the UTXOs' values. * @param depositorPrivateKey Bitcoin private key of the depositor. Must * be able to unlock input UTXOs. * @returns The outcome consisting of: * - the deposit transaction hash, * - the deposit UTXO produced by this transaction. * - the deposit transaction in the raw format + * @dev UTXOs are selected for transaction funding based on their types. UTXOs + * with unsupported types are skipped. The selection process stops once + * the sum of the chosen UTXOs meets the required funding amount. + * @throws {Error} When the sum of the selected UTXOs is insufficient to cover + * the deposit amount and transaction fee. */ async assembleTransaction( + bitcoinNetwork: BitcoinNetwork, amount: BigNumber, inputUtxos: (BitcoinUtxo & BitcoinRawTx)[], + fee: BigNumber, depositorPrivateKey: string ): Promise<{ transactionHash: BitcoinTxHash depositUtxo: BitcoinUtxo rawTransaction: BitcoinRawTx }> { - const depositorKeyRing = - BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) - const depositorAddress = depositorKeyRing.getAddress("string") - - const inputCoins = inputUtxos.map((utxo) => - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), - utxo.outputIndex, - -1 - ) + const network = toBitcoinJsLibNetwork(bitcoinNetwork) + // eslint-disable-next-line new-cap + const depositorKeyPair = ECPairFactory(tinysecp).fromWIF( + depositorPrivateKey, + network ) - const transaction = new bcoin.MTX() + const psbt = new Psbt({ network }) + psbt.setVersion(1) + + const totalExpenses = amount.add(fee) + let totalInputValue = BigNumber.from(0) + + for (const utxo of inputUtxos) { + const previousOutput = Transaction.fromHex(utxo.transactionHex).outs[ + utxo.outputIndex + ] + const previousOutputValue = previousOutput.value + const previousOutputScript = previousOutput.script + + // TODO: Add support for other utxo types along with unit tests for the + // given type. + if (BitcoinScriptUtils.isP2WPKHScript(previousOutputScript)) { + psbt.addInput({ + hash: utxo.transactionHash.reverse().toBuffer(), + index: utxo.outputIndex, + witnessUtxo: { + script: previousOutputScript, + value: previousOutputValue, + }, + }) + + totalInputValue = totalInputValue.add(utxo.value) + if (totalInputValue.gte(totalExpenses)) { + break + } + } + // Skip UTXO if the type is unsupported. + } - const scriptHash = await this.script.getHash() + // Sum of the selected UTXOs must be equal to or greater than the deposit + // amount plus fee. + if (totalInputValue.lt(totalExpenses)) { + throw new Error("Not enough funds in selected UTXOs to fund transaction") + } - transaction.addOutput({ - script: this.script.witness - ? bcoin.Script.fromProgram(0, scriptHash) - : bcoin.Script.fromScripthash(scriptHash), + // Add deposit output. + psbt.addOutput({ + address: await this.script.deriveAddress(bitcoinNetwork), value: amount.toNumber(), }) - await transaction.fund(inputCoins, { - rate: null, // set null explicitly to always use the default value - changeAddress: depositorAddress, - subtractFee: false, // do not subtract the fee from outputs - }) + // Add change output if needed. + const changeValue = totalInputValue.sub(totalExpenses) + if (changeValue.gt(0)) { + const depositorAddress = BitcoinAddressConverter.publicKeyToAddress( + Hex.from(depositorKeyPair.publicKey), + bitcoinNetwork + ) + psbt.addOutput({ + address: depositorAddress, + value: changeValue.toNumber(), + }) + } - transaction.sign(depositorKeyRing) + psbt.signAllInputs(depositorKeyPair) + psbt.finalizeAllInputs() - const transactionHash = BitcoinTxHash.from(transaction.txid()) + const transaction = psbt.extractTransaction() + const transactionHash = BitcoinTxHash.from(transaction.getId()) return { transactionHash, @@ -96,7 +150,7 @@ export class DepositFunding { value: amount, }, rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), + transactionHex: transaction.toHex(), }, } } @@ -108,30 +162,35 @@ export class DepositFunding { * some UTXOs that can be used as input. It is also caller's responsibility * to ensure the given deposit is funded exactly once. * @param amount Deposit amount in satoshis. + * @param inputUtxos UTXOs to be used for funding the deposit transaction. So + * far only P2WPKH UTXO inputs are supported. + * @param fee The value that should be subtracted from the sum of the UTXOs + * values and used as the transaction fee. * @param depositorPrivateKey Bitcoin private key of the depositor. * @param bitcoinClient Bitcoin client used to interact with the network. * @returns The outcome consisting of: * - the deposit transaction hash, * - the deposit UTXO produced by this transaction. + * @dev UTXOs are selected for transaction funding based on their types. UTXOs + * with unsupported types are skipped. The selection process stops once + * the sum of the chosen UTXOs meets the required funding amount. + * Be aware that the function will attempt to broadcast the transaction, + * although successful broadcast is not guaranteed. + * @throws {Error} When the sum of the selected UTXOs is insufficient to cover + * the deposit amount and transaction fee. */ async submitTransaction( amount: BigNumber, + inputUtxos: BitcoinUtxo[], + fee: BigNumber, depositorPrivateKey: string, bitcoinClient: BitcoinClient ): Promise<{ transactionHash: BitcoinTxHash depositUtxo: BitcoinUtxo }> { - const depositorKeyRing = - BitcoinPrivateKeyUtils.createKeyRing(depositorPrivateKey) - const depositorAddress = depositorKeyRing.getAddress("string") - - const utxos = await bitcoinClient.findAllUnspentTransactionOutputs( - depositorAddress - ) - const utxosWithRaw: (BitcoinUtxo & BitcoinRawTx)[] = [] - for (const utxo of utxos) { + for (const utxo of inputUtxos) { const utxoRawTransaction = await bitcoinClient.getRawTransaction( utxo.transactionHash ) @@ -142,9 +201,20 @@ export class DepositFunding { }) } + const bitcoinNetwork = await bitcoinClient.getNetwork() + const { transactionHash, depositUtxo, rawTransaction } = - await this.assembleTransaction(amount, utxosWithRaw, depositorPrivateKey) + await this.assembleTransaction( + bitcoinNetwork, + amount, + utxosWithRaw, + fee, + depositorPrivateKey + ) + // Note that `broadcast` may fail silently (i.e. no error will be returned, + // even if the transaction is rejected by other nodes and does not enter the + // mempool, for example due to an UTXO being already spent). await bitcoinClient.broadcast(rawTransaction) return { diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index c695fbee4..c2d1bb007 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -65,7 +65,7 @@ describe("Bitcoin", () => { }) describe("BitcoinHashUtils", () => { - const { computeHash160, computeHash256, hashLEToBigNumber } = + const { computeHash160, computeHash256, hashLEToBigNumber, computeSha256 } = BitcoinHashUtils describe("computeHash160", () => { @@ -104,6 +104,21 @@ describe("Bitcoin", () => { expect(hashLEToBigNumber(hash)).to.equal(expectedBigNumber) }) }) + + describe("computeSha256", () => { + it("should compute hash256 correctly", () => { + const hexValue = Hex.from( + "03474444cca71c678f5019d16782b6522735717a94602085b4adf707b465c36ca8" + ) + const expectedSha256 = Hex.from( + "c62e5cb26c97cb52fea7f9965e9ea1f8d41c97773688aa88674e64629fc02901" + ) + + expect(computeSha256(hexValue).toString()).to.be.equal( + expectedSha256.toString() + ) + }) + }) }) describe("BitcoinAddressConverter", () => { diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index d534d37e7..ec135e568 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -9,16 +9,16 @@ import { } from "./data/deposit" import { BitcoinLocktimeUtils, + BitcoinNetwork, BitcoinRawTx, BitcoinTxHash, BitcoinUtxo, - extractBitcoinRawTxVectors, - DepositReceipt, - EthereumAddress, - BitcoinNetwork, + Deposit, DepositFunding, + DepositReceipt, DepositScript, - Deposit, + EthereumAddress, + extractBitcoinRawTxVectors, } from "../src" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import bcoin from "bcoin" @@ -209,16 +209,8 @@ describe("Deposit", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { - bcoin.set("testnet") - bitcoinClient = new MockBitcoinClient() - // Tie used testnetAddress with testnetUTXO to use it during deposit - // creation. - const utxos = new Map() - utxos.set(testnetAddress, [testnetUTXO]) - bitcoinClient.unspentTransactionOutputs = utxos - // Tie testnetTransaction to testnetUTXO. This is needed since // submitDepositTransaction attach transaction data to each UTXO. const rawTransactions = new Map() @@ -234,6 +226,8 @@ describe("Deposit", () => { let depositUtxo: BitcoinUtxo beforeEach(async () => { + const fee = BigNumber.from(1520) + const depositFunding = DepositFunding.fromScript( DepositScript.fromReceipt(deposit, true) ) @@ -241,6 +235,8 @@ describe("Deposit", () => { ;({ transactionHash, depositUtxo } = await depositFunding.submitTransaction( depositAmount, + [testnetUTXO], + fee, testnetPrivateKey, bitcoinClient )) @@ -275,6 +271,8 @@ describe("Deposit", () => { let depositUtxo: BitcoinUtxo beforeEach(async () => { + const fee = BigNumber.from(1410) + const depositFunding = DepositFunding.fromScript( DepositScript.fromReceipt(deposit, false) ) @@ -282,6 +280,8 @@ describe("Deposit", () => { ;({ transactionHash, depositUtxo } = await depositFunding.submitTransaction( depositAmount, + [testnetUTXO], + fee, testnetPrivateKey, bitcoinClient )) @@ -319,6 +319,8 @@ describe("Deposit", () => { let transaction: BitcoinRawTx beforeEach(async () => { + const fee = BigNumber.from(1520) + const depositFunding = DepositFunding.fromScript( DepositScript.fromReceipt(deposit, true) ) @@ -328,8 +330,10 @@ describe("Deposit", () => { depositUtxo, rawTransaction: transaction, } = await depositFunding.assembleTransaction( + BitcoinNetwork.Testnet, depositAmount, [testnetUTXO], + fee, testnetPrivateKey )) }) @@ -419,6 +423,8 @@ describe("Deposit", () => { let transaction: BitcoinRawTx beforeEach(async () => { + const fee = BigNumber.from(1410) + const depositFunding = DepositFunding.fromScript( DepositScript.fromReceipt(deposit, false) ) @@ -428,8 +434,10 @@ describe("Deposit", () => { depositUtxo, rawTransaction: transaction, } = await depositFunding.assembleTransaction( + BitcoinNetwork.Testnet, depositAmount, [testnetUTXO], + fee, testnetPrivateKey )) }) @@ -664,14 +672,18 @@ describe("Deposit", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { + const fee = BigNumber.from(1520) + const depositFunding = DepositFunding.fromScript( DepositScript.fromReceipt(deposit) ) // Create a deposit transaction. const result = await depositFunding.assembleTransaction( + BitcoinNetwork.Testnet, depositAmount, [testnetUTXO], + fee, testnetPrivateKey ) From 3dfad7aec1bb07d506c0413bd64b7daeb7710c61 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 10 Oct 2023 11:42:33 +0200 Subject: [PATCH 29/43] Integrate `bitcoinjs-lib` changes around redemptions Here we pull changes from https://github.com/keep-network/tbtc-v2/pull/703 --- typescript/src/lib/bitcoin/ecdsa-key.ts | 31 +++--- typescript/src/services/deposits/funding.ts | 9 +- typescript/src/services/deposits/refund.ts | 11 +- .../src/services/maintenance/wallet-tx.ts | 103 ++++++++++-------- typescript/test/redemption.test.ts | 14 ++- 5 files changed, 95 insertions(+), 73 deletions(-) diff --git a/typescript/src/lib/bitcoin/ecdsa-key.ts b/typescript/src/lib/bitcoin/ecdsa-key.ts index 16a6b1ec7..f7b845840 100644 --- a/typescript/src/lib/bitcoin/ecdsa-key.ts +++ b/typescript/src/lib/bitcoin/ecdsa-key.ts @@ -1,7 +1,8 @@ -import bcoin from "bcoin" -import wif from "wif" import { BigNumber } from "ethers" import { Hex } from "../utils" +import { ECPairFactory, ECPairInterface } from "ecpair" +import * as tinysecp from "tiny-secp256k1" +import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./network" /** * Checks whether given public key is a compressed Bitcoin public key. @@ -59,25 +60,25 @@ export const BitcoinPublicKeyUtils = { } /** - * Creates a Bitcoin key ring based on the given private key. - * @param privateKey Private key that should be used to create the key ring - * @param witness Flag indicating whether the key ring will create witness - * or non-witness addresses + * Creates a Bitcoin key pair based on the given private key. + * @param privateKey Private key that should be used to create the key pair. + * @param bitcoinNetwork Bitcoin network the given key pair is relevant for. * @returns Bitcoin key ring. */ -function createKeyRing(privateKey: string, witness: boolean = true): any { - const decodedPrivateKey = wif.decode(privateKey) - - return new bcoin.KeyRing({ - witness: witness, - privateKey: decodedPrivateKey.privateKey, - compressed: decodedPrivateKey.compressed, - }) +function createKeyPair( + privateKey: string, + bitcoinNetwork: BitcoinNetwork +): ECPairInterface { + // eslint-disable-next-line new-cap + return ECPairFactory(tinysecp).fromWIF( + privateKey, + toBitcoinJsLibNetwork(bitcoinNetwork) + ) } /** * Utility functions allowing to perform Bitcoin ECDSA public keys. */ export const BitcoinPrivateKeyUtils = { - createKeyRing, + createKeyPair, } diff --git a/typescript/src/services/deposits/funding.ts b/typescript/src/services/deposits/funding.ts index ff250d096..c520f5e83 100644 --- a/typescript/src/services/deposits/funding.ts +++ b/typescript/src/services/deposits/funding.ts @@ -3,6 +3,7 @@ import { BitcoinAddressConverter, BitcoinClient, BitcoinNetwork, + BitcoinPrivateKeyUtils, BitcoinRawTx, BitcoinScriptUtils, BitcoinTxHash, @@ -11,8 +12,6 @@ import { } from "../../lib/bitcoin" import { BigNumber } from "ethers" import { Psbt, Transaction } from "bitcoinjs-lib" -import { ECPairFactory } from "ecpair" -import * as tinysecp from "tiny-secp256k1" import { Hex } from "../../lib/utils" /** @@ -72,10 +71,10 @@ export class DepositFunding { rawTransaction: BitcoinRawTx }> { const network = toBitcoinJsLibNetwork(bitcoinNetwork) - // eslint-disable-next-line new-cap - const depositorKeyPair = ECPairFactory(tinysecp).fromWIF( + + const depositorKeyPair = BitcoinPrivateKeyUtils.createKeyPair( depositorPrivateKey, - network + bitcoinNetwork ) const psbt = new Psbt({ network }) diff --git a/typescript/src/services/deposits/refund.ts b/typescript/src/services/deposits/refund.ts index 4582c7f1c..6b896e0e9 100644 --- a/typescript/src/services/deposits/refund.ts +++ b/typescript/src/services/deposits/refund.ts @@ -1,7 +1,6 @@ import bcoin from "bcoin" import { BigNumber } from "ethers" import { - BitcoinPrivateKeyUtils, BitcoinRawTx, BitcoinClient, BitcoinTxHash, @@ -11,6 +10,7 @@ import { } from "../../lib/bitcoin" import { validateDepositReceipt } from "../../lib/contracts" import { DepositScript } from "./" +import wif from "wif" /** * Component allowing to craft and submit the Bitcoin refund transaction using @@ -106,8 +106,13 @@ export class DepositRefund { }> { validateDepositReceipt(this.script.receipt) - const refunderKeyRing = - BitcoinPrivateKeyUtils.createKeyRing(refunderPrivateKey) + const decodedPrivateKey = wif.decode(refunderPrivateKey) + + const refunderKeyRing = new bcoin.KeyRing({ + witness: true, + privateKey: decodedPrivateKey.privateKey, + compressed: decodedPrivateKey.compressed, + }) const transaction = new bcoin.MTX() diff --git a/typescript/src/services/maintenance/wallet-tx.ts b/typescript/src/services/maintenance/wallet-tx.ts index 4c017bebb..552929510 100644 --- a/typescript/src/services/maintenance/wallet-tx.ts +++ b/typescript/src/services/maintenance/wallet-tx.ts @@ -17,10 +17,7 @@ import { RedemptionRequest, TBTCContracts, } from "../../lib/contracts" -import bcoin from "bcoin" import { DepositScript } from "../deposits" -import { ECPairFactory } from "ecpair" -import * as tinysecp from "tiny-secp256k1" import { Hex } from "../../lib/utils" import { payments, @@ -29,6 +26,7 @@ import { Stack, Transaction, TxOutput, + Psbt, } from "bitcoinjs-lib" /** @@ -189,11 +187,9 @@ class DepositSweep { ) } - const network = toBitcoinJsLibNetwork(bitcoinNetwork) - // eslint-disable-next-line new-cap - const walletKeyPair = ECPairFactory(tinysecp).fromWIF( + const walletKeyPair = BitcoinPrivateKeyUtils.createKeyPair( walletPrivateKey, - network + bitcoinNetwork ) const walletAddress = BitcoinAddressConverter.publicKeyToAddress( Hex.from(walletKeyPair.publicKey), @@ -566,11 +562,14 @@ class Redemption { transactionHex: mainUtxoRawTransaction.transactionHex, } - const walletPublicKey = BitcoinPrivateKeyUtils.createKeyRing( - walletPrivateKey + const bitcoinNetwork = await this.bitcoinClient.getNetwork() + + const walletKeyPair = BitcoinPrivateKeyUtils.createKeyPair( + walletPrivateKey, + bitcoinNetwork ) - .getPublicKey() - .toString("hex") + + const walletPublicKey = walletKeyPair.publicKey.toString("hex") const redemptionRequests: RedemptionRequest[] = [] @@ -593,6 +592,7 @@ class Redemption { const { transactionHash, newMainUtxo, rawTransaction } = await this.assembleTransaction( + bitcoinNetwork, walletPrivateKey, mainUtxoWithRaw, redemptionRequests @@ -617,6 +617,7 @@ class Redemption { * - there is at least one redemption * - the `requestedAmount` in each redemption request is greater than * the sum of its `txFee` and `treasuryFee` + * @param bitcoinNetwork The target Bitcoin network. * @param walletPrivateKey - The private key of the wallet in the WIF format * @param mainUtxo - The main UTXO of the wallet. Must match the main UTXO held * by the on-chain Bridge contract @@ -627,6 +628,7 @@ class Redemption { * - the redemption transaction in the raw format */ async assembleTransaction( + bitcoinNetwork: BitcoinNetwork, walletPrivateKey: string, mainUtxo: BitcoinUtxo & BitcoinRawTx, redemptionRequests: RedemptionRequest[] @@ -639,22 +641,45 @@ class Redemption { throw new Error("There must be at least one request to redeem") } - const walletKeyRing = BitcoinPrivateKeyUtils.createKeyRing( + const walletKeyPair = BitcoinPrivateKeyUtils.createKeyPair( walletPrivateKey, + bitcoinNetwork + ) + const walletAddress = BitcoinAddressConverter.publicKeyToAddress( + Hex.from(walletKeyPair.publicKey), + bitcoinNetwork, this.witness ) - const walletAddress = walletKeyRing.getAddress("string") - - // Use the main UTXO as the single transaction input - const inputCoins = [ - bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(mainUtxo.transactionHex, "hex"), - mainUtxo.outputIndex, - -1 - ), - ] - const transaction = new bcoin.MTX() + const network = toBitcoinJsLibNetwork(bitcoinNetwork) + const psbt = new Psbt({ network }) + psbt.setVersion(1) + + // Add input (current main UTXO). + const previousOutput = Transaction.fromHex(mainUtxo.transactionHex).outs[ + mainUtxo.outputIndex + ] + const previousOutputScript = previousOutput.script + const previousOutputValue = previousOutput.value + + if (BitcoinScriptUtils.isP2PKHScript(previousOutputScript)) { + psbt.addInput({ + hash: mainUtxo.transactionHash.reverse().toBuffer(), + index: mainUtxo.outputIndex, + nonWitnessUtxo: Buffer.from(mainUtxo.transactionHex, "hex"), + }) + } else if (BitcoinScriptUtils.isP2WPKHScript(previousOutputScript)) { + psbt.addInput({ + hash: mainUtxo.transactionHash.reverse().toBuffer(), + index: mainUtxo.outputIndex, + witnessUtxo: { + script: previousOutputScript, + value: previousOutputValue, + }, + }) + } else { + throw new Error("Unexpected main UTXO type") + } let txTotalFee = BigNumber.from(0) let totalOutputsValue = BigNumber.from(0) @@ -673,44 +698,34 @@ class Redemption { // Add the fee for this particular request to the overall transaction fee txTotalFee = txTotalFee.add(request.txMaxFee) - transaction.addOutput({ - script: bcoin.Script.fromRaw( - Buffer.from(request.redeemerOutputScript, "hex") - ), + psbt.addOutput({ + script: Buffer.from(request.redeemerOutputScript, "hex"), value: outputValue.toNumber(), }) } - // If there is a change output, add it explicitly to the transaction. - // If we did not add this output explicitly, the bcoin library would add it - // anyway during funding, but if the value of the change output was very low, - // the library would consider it "dust" and add it to the fee rather than - // create a new output. + // If there is a change output, add it to the transaction. const changeOutputValue = mainUtxo.value .sub(totalOutputsValue) .sub(txTotalFee) if (changeOutputValue.gt(0)) { - transaction.addOutput({ - script: bcoin.Script.fromAddress(walletAddress), + psbt.addOutput({ + address: walletAddress, value: changeOutputValue.toNumber(), }) } - await transaction.fund(inputCoins, { - changeAddress: walletAddress, - hardFee: txTotalFee.toNumber(), - subtractFee: false, - }) - - transaction.sign(walletKeyRing) + psbt.signAllInputs(walletKeyPair) + psbt.finalizeAllInputs() - const transactionHash = BitcoinTxHash.from(transaction.txid()) + const transaction = psbt.extractTransaction() + const transactionHash = BitcoinTxHash.from(transaction.getId()) // If there is a change output, it will be the new wallet's main UTXO. const newMainUtxo = changeOutputValue.gt(0) ? { transactionHash, // It was the last output added to the transaction. - outputIndex: transaction.outputs.length - 1, + outputIndex: transaction.outs.length - 1, value: changeOutputValue, } : undefined @@ -719,7 +734,7 @@ class Redemption { transactionHash, newMainUtxo, rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), + transactionHex: transaction.toHex(), }, } } diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index 468360684..371662f2c 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -61,8 +61,6 @@ describe("Redemption", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { - bcoin.set("testnet") - tbtcContracts = new MockTBTCContracts() bitcoinClient = new MockBitcoinClient() @@ -818,8 +816,6 @@ describe("Redemption", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { - bcoin.set("testnet") - tbtcContracts = new MockTBTCContracts() bitcoinClient = new MockBitcoinClient() }) @@ -1272,6 +1268,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -1396,6 +1393,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -1519,6 +1517,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -1642,6 +1641,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -1764,6 +1764,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -1930,6 +1931,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -2045,6 +2047,7 @@ describe("Redemption", () => { newMainUtxo, rawTransaction: transaction, } = await walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, redemptionRequests @@ -2140,6 +2143,7 @@ describe("Redemption", () => { await expect( walletTx.redemption.assembleTransaction( + BitcoinNetwork.Testnet, walletPrivateKey, data.mainUtxo, [] // empty list of redemption requests @@ -2166,8 +2170,6 @@ describe("Redemption", () => { let maintenanceService: MaintenanceService beforeEach(async () => { - bcoin.set("testnet") - bitcoinClient = new MockBitcoinClient() tbtcContracts = new MockTBTCContracts() From 162ce256b4478cb3aabe5baa3fcdab691a95b74c Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 10 Oct 2023 12:13:38 +0200 Subject: [PATCH 30/43] Integrate `bitcoinjs-lib` changes around deposit refund Here we pull changes from https://github.com/keep-network/tbtc-v2/pull/706 --- typescript/src/services/deposits/refund.ts | 231 ++++++++++----------- typescript/test/deposit-refund.test.ts | 3 - 2 files changed, 110 insertions(+), 124 deletions(-) diff --git a/typescript/src/services/deposits/refund.ts b/typescript/src/services/deposits/refund.ts index 6b896e0e9..5c4f0dcb4 100644 --- a/typescript/src/services/deposits/refund.ts +++ b/typescript/src/services/deposits/refund.ts @@ -1,16 +1,24 @@ -import bcoin from "bcoin" import { BigNumber } from "ethers" import { - BitcoinRawTx, + BitcoinAddressConverter, BitcoinClient, - BitcoinTxHash, - BitcoinUtxo, BitcoinHashUtils, + BitcoinNetwork, + BitcoinPrivateKeyUtils, BitcoinPublicKeyUtils, + BitcoinRawTx, + BitcoinScriptUtils, + BitcoinTxHash, + BitcoinUtxo, } from "../../lib/bitcoin" import { validateDepositReceipt } from "../../lib/contracts" import { DepositScript } from "./" -import wif from "wif" +import { + Signer, + Transaction, + script as btcjsscript, + Stack, +} from "bitcoinjs-lib" /** * Component allowing to craft and submit the Bitcoin refund transaction using @@ -67,7 +75,10 @@ export class DepositRefund { transactionHex: utxoRawTransaction.transactionHex, } + const bitcoinNetwork = await bitcoinClient.getNetwork() + const { transactionHash, rawTransaction } = await this.assembleTransaction( + bitcoinNetwork, fee, utxoWithRaw, refunderAddress, @@ -84,6 +95,7 @@ export class DepositRefund { /** * Assembles a Bitcoin P2(W)PKH deposit refund transaction. + * @param bitcoinNetwork - The target Bitcoin network. * @param fee - the value that will be subtracted from the deposit UTXO being * refunded and used as the transaction fee. * @param utxo - UTXO that was created during depositing that needs be refunded. @@ -96,6 +108,7 @@ export class DepositRefund { * - the refund transaction in the raw format. */ async assembleTransaction( + bitcoinNetwork: BitcoinNetwork, fee: BigNumber, utxo: BitcoinUtxo & BitcoinRawTx, refunderAddress: string, @@ -106,36 +119,23 @@ export class DepositRefund { }> { validateDepositReceipt(this.script.receipt) - const decodedPrivateKey = wif.decode(refunderPrivateKey) - - const refunderKeyRing = new bcoin.KeyRing({ - witness: true, - privateKey: decodedPrivateKey.privateKey, - compressed: decodedPrivateKey.compressed, - }) + const refunderKeyPair = BitcoinPrivateKeyUtils.createKeyPair( + refunderPrivateKey, + bitcoinNetwork + ) - const transaction = new bcoin.MTX() + const outputValue = utxo.value.sub(fee) - transaction.addOutput({ - script: bcoin.Script.fromAddress(refunderAddress), - value: utxo.value.toNumber(), - }) + const transaction = new Transaction() - const inputCoin = bcoin.Coin.fromTX( - bcoin.MTX.fromRaw(utxo.transactionHex, "hex"), - utxo.outputIndex, - -1 + transaction.addInput( + utxo.transactionHash.reverse().toBuffer(), + utxo.outputIndex ) - await transaction.fund([inputCoin], { - changeAddress: refunderAddress, - hardFee: fee.toNumber(), - subtractFee: true, - }) - - if (transaction.outputs.length != 1) { - throw new Error("Deposit refund transaction must have only one output") - } + const outputScript = + BitcoinAddressConverter.addressToOutputScript(refunderAddress) + transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber()) // In order to be able to spend the UTXO being refunded the transaction's // locktime must be set to a value equal to or higher than the refund locktime. @@ -144,65 +144,56 @@ export class DepositRefund { transaction.locktime = locktimeToUnixTimestamp( this.script.receipt.refundLocktime ) - transaction.inputs[0].sequence = 0xfffffffe + transaction.ins[0].sequence = 0xfffffffe // Sign the input - const previousOutpoint = transaction.inputs[0].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) - const previousScript = previousOutput.script - - if (previousScript.isScripthash()) { - // P2SH UTXO deposit input - await this.signP2SHDepositInput(transaction, 0, refunderKeyRing) - } else if (previousScript.isWitnessScripthash()) { - // P2WSH UTXO deposit input - await this.signP2WSHDepositInput(transaction, 0, refunderKeyRing) + const previousOutput = Transaction.fromHex(utxo.transactionHex).outs[ + utxo.outputIndex + ] + const previousOutputValue = previousOutput.value + const previousOutputScript = previousOutput.script + + if (BitcoinScriptUtils.isP2SHScript(previousOutputScript)) { + // P2SH deposit UTXO + await this.signP2SHDepositInput(transaction, 0, refunderKeyPair) + } else if (BitcoinScriptUtils.isP2WSHScript(previousOutputScript)) { + // P2WSH deposit UTXO + await this.signP2WSHDepositInput( + transaction, + 0, + previousOutputValue, + refunderKeyPair + ) } else { throw new Error("Unsupported UTXO script type") } - // Verify the transaction by executing its input scripts. - const tx = transaction.toTX() - if (!tx.verify(transaction.view)) { - throw new Error("Transaction verification failure") - } - - const transactionHash = BitcoinTxHash.from(transaction.txid()) + const transactionHash = BitcoinTxHash.from(transaction.getId()) return { transactionHash, rawTransaction: { - transactionHex: transaction.toRaw().toString("hex"), + transactionHex: transaction.toHex(), }, } } /** - * Creates data needed to sign a deposit input to be refunded. - * @param transaction - Mutable transaction containing the input to be refunded. - * @param inputIndex - Index that points to the input. - * @param refunderKeyRing - Key ring created using the refunder's private key. - * @returns Data needed to sign the input. + * Assembles the deposit script based on the given deposit details. Performs + * validations on values and key formats. + * @param refunderKeyPair - Signer object containing the refunder's key pair. + * @returns A Promise resolving to the assembled deposit script as a Buffer. + * @throws Error if there are discrepancies in values or key formats. */ - private async prepareInputSignData( - transaction: any, - inputIndex: number, - refunderKeyRing: any - ): Promise<{ - refunderPublicKey: string - depositScript: any - previousOutputValue: number - }> { - const previousOutpoint = transaction.inputs[inputIndex].prevout - const previousOutput = transaction.view.getOutput(previousOutpoint) + private async prepareDepositScript(refunderKeyPair: Signer): Promise { + const refunderPublicKey = refunderKeyPair.publicKey.toString("hex") - const refunderPublicKey = refunderKeyRing.getPublicKey("hex") if ( - BitcoinHashUtils.computeHash160(refunderKeyRing.getPublicKey("hex")) != + BitcoinHashUtils.computeHash160(refunderPublicKey) != this.script.receipt.refundPublicKeyHash ) { throw new Error( - "Refund public key does not correspond to the refunder private key" + "Refund public key does not correspond to wallet private key" ) } @@ -210,84 +201,82 @@ export class DepositRefund { throw new Error("Refunder public key must be compressed") } - const depositScript = bcoin.Script.fromRaw( - Buffer.from(await this.script.getPlainText(), "hex") - ) - - return { - refunderPublicKey: refunderPublicKey, - depositScript: depositScript, - previousOutputValue: previousOutput.value, - } + return Buffer.from(await this.script.getPlainText(), "hex") } /** - * Creates and sets `scriptSig` for the transaction input at the given index by - * combining signature, refunder's public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param refunderKeyRing - Key ring created using the refunder's private key. - * @returns Empty return. + * Signs a P2SH deposit transaction input and sets the `scriptSig`. + * @param transaction - The transaction containing the input to be signed. + * @param inputIndex - Index pointing to the input within the transaction. + * @param refunderKeyPair - A Signer object with the refunder's public and private + * key pair. + * @returns An empty promise upon successful signing. */ private async signP2SHDepositInput( - transaction: any, + transaction: Transaction, inputIndex: number, - refunderKeyRing: any + refunderKeyPair: Signer ) { - const { refunderPublicKey, depositScript, previousOutputValue } = - await this.prepareInputSignData(transaction, inputIndex, refunderKeyRing) + const depositScript = await this.prepareDepositScript(refunderKeyPair) - const signature: Buffer = transaction.signature( + const sigHashType = Transaction.SIGHASH_ALL + + const sigHash = transaction.hashForSignature( inputIndex, depositScript, - previousOutputValue, - refunderKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 0 // legacy sighash version + sigHashType ) - const scriptSig = new bcoin.Script() - scriptSig.clear() - scriptSig.pushData(signature) - scriptSig.pushData(Buffer.from(refunderPublicKey, "hex")) - scriptSig.pushData(depositScript.toRaw()) - scriptSig.compile() - - transaction.inputs[inputIndex].script = scriptSig + + const signature = btcjsscript.signature.encode( + refunderKeyPair.sign(sigHash), + sigHashType + ) + + const scriptSig: Stack = [] + scriptSig.push(signature) + scriptSig.push(refunderKeyPair.publicKey) + scriptSig.push(depositScript) + + transaction.ins[inputIndex].script = btcjsscript.compile(scriptSig) } /** - * Creates and sets witness script for the transaction input at the given index - * by combining signature, refunder public key and deposit script. - * @param transaction - Mutable transaction containing the input to be signed. - * @param inputIndex - Index that points to the input to be signed. - * @param refunderKeyRing - Key ring created using the refunder's private key. - * @returns Empty return. + * Signs a P2WSH deposit transaction input and sets the witness script. + * @param transaction - The transaction containing the input to be signed. + * @param inputIndex - Index pointing to the input within the transaction. + * @param previousOutputValue - The value from the previous transaction output. + * @param refunderKeyPair - A Signer object with the refunder's public and private + * key pair. + * @returns An empty promise upon successful signing. */ private async signP2WSHDepositInput( - transaction: any, + transaction: Transaction, inputIndex: number, - refunderKeyRing: any + previousOutputValue: number, + refunderKeyPair: Signer ) { - const { refunderPublicKey, depositScript, previousOutputValue } = - await this.prepareInputSignData(transaction, inputIndex, refunderKeyRing) + const depositScript = await this.prepareDepositScript(refunderKeyPair) - const signature: Buffer = transaction.signature( + const sigHashType = Transaction.SIGHASH_ALL + + const sigHash = transaction.hashForWitnessV0( inputIndex, depositScript, previousOutputValue, - refunderKeyRing.privateKey, - bcoin.Script.hashType.ALL, - 1 // segwit sighash version + sigHashType + ) + + const signature = btcjsscript.signature.encode( + refunderKeyPair.sign(sigHash), + sigHashType ) - const witness = new bcoin.Witness() - witness.clear() - witness.pushData(signature) - witness.pushData(Buffer.from(refunderPublicKey, "hex")) - witness.pushData(depositScript.toRaw()) - witness.compile() + const witness: Buffer[] = [] + witness.push(signature) + witness.push(refunderKeyPair.publicKey) + witness.push(depositScript) - transaction.inputs[inputIndex].witness = witness + transaction.ins[inputIndex].witness = witness } } diff --git a/typescript/test/deposit-refund.test.ts b/typescript/test/deposit-refund.test.ts index 6825e6fa8..722d74566 100644 --- a/typescript/test/deposit-refund.test.ts +++ b/typescript/test/deposit-refund.test.ts @@ -1,6 +1,5 @@ import { BigNumber } from "ethers" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import bcoin from "bcoin" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) @@ -23,8 +22,6 @@ describe("Refund", () => { let bitcoinClient: MockBitcoinClient beforeEach(async () => { - bcoin.set("testnet") - bitcoinClient = new MockBitcoinClient() }) From 398788767dbe0ee2e5a22c2d0351cdd8729aa7d6 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 10 Oct 2023 13:44:17 +0200 Subject: [PATCH 31/43] Remove `bcoin` completely Here we pull changes from https://github.com/keep-network/tbtc-v2/pull/707 --- typescript/package.json | 7 +- typescript/src/lib/bitcoin/address.ts | 74 ++++--- typescript/src/lib/bitcoin/network.ts | 21 -- typescript/src/lib/bitcoin/tx.ts | 47 +++-- typescript/src/lib/electrum/client.ts | 47 +++-- .../src/services/deposits/deposits-service.ts | 5 +- typescript/src/services/deposits/refund.ts | 6 +- .../src/services/maintenance/wallet-tx.ts | 6 +- .../redemptions/redemptions-service.ts | 16 +- typescript/test/bitcoin-network.test.ts | 32 +-- typescript/test/bitcoin.test.ts | 164 +++++++++++---- typescript/test/data/redemption.ts | 19 +- typescript/test/deposit-sweep.test.ts | 111 ++++------ typescript/test/deposit.test.ts | 28 ++- typescript/test/redemption.test.ts | 99 ++++----- typescript/test/utils/helpers.ts | 6 +- typescript/yarn.lock | 195 +----------------- 17 files changed, 360 insertions(+), 523 deletions(-) diff --git a/typescript/package.json b/typescript/package.json index fb929012a..a518d97fe 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -13,8 +13,7 @@ "test": "mocha --exit --recursive 'test/**/*.test.ts'", "typechain": "rm -rf ./typechain && for i in $npm_package_config_contracts; do typechain --target ethers-v5 --out-dir ./typechain $i; done && rm ./typechain/index.ts", "build": "npm run typechain && tsc --project tsconfig.build.json", - "dev": "tsc --project tsconfig.build.json --watch", - "postinstall": "npm rebuild bcrypto" + "dev": "tsc --project tsconfig.build.json --watch" }, "files": ["dist"], "config": { @@ -23,7 +22,6 @@ "dependencies": { "@keep-network/ecdsa": "development", "@keep-network/tbtc-v2": "development", - "bcoin": "git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8", "bitcoinjs-lib": "6.0.2", "bufio": "^1.0.6", "ecpair": "^2.1.0", @@ -57,8 +55,5 @@ }, "engines": { "node": ">=14 <15" - }, - "browser": { - "bcoin": "bcoin/lib/bcoin-browser" } } diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts index cc10aebda..3d5dd2594 100644 --- a/typescript/src/lib/bitcoin/address.ts +++ b/typescript/src/lib/bitcoin/address.ts @@ -1,11 +1,6 @@ -import bcoin, { Script } from "bcoin" +import { address as btcjsaddress, payments } from "bitcoinjs-lib" import { Hex } from "../utils" -import { - BitcoinNetwork, - toBcoinNetwork, - toBitcoinJsLibNetwork, -} from "./network" -import { payments } from "bitcoinjs-lib" +import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./network" /** * Creates the Bitcoin address from the public key. Supports SegWit (P2WPKH) and @@ -34,67 +29,84 @@ export function publicKeyToAddress( /** * Converts a public key hash into a P2PKH/P2WPKH address. - * @param publicKeyHash - public key hash that will be encoded. Must be an + * @param publicKeyHash Public key hash that will be encoded. Must be an * unprefixed hex string (without 0x prefix). - * @param witness - If true, a witness public key hash will be encoded and + * @param witness If true, a witness public key hash will be encoded and * P2WPKH address will be returned. Returns P2PKH address otherwise - * @param network - Network the address should be encoded for. + * @param bitcoinNetwork Network the address should be encoded for. * @returns P2PKH or P2WPKH address encoded from the given public key hash * @throws Throws an error if network is not supported. */ function publicKeyHashToAddress( publicKeyHash: string, witness: boolean, - network: BitcoinNetwork + bitcoinNetwork: BitcoinNetwork ): string { - const buffer = Buffer.from(publicKeyHash, "hex") - const bcoinNetwork = toBcoinNetwork(network) + const hash = Buffer.from(publicKeyHash, "hex") + const network = toBitcoinJsLibNetwork(bitcoinNetwork) return witness - ? bcoin.Address.fromWitnessPubkeyhash(buffer).toString(bcoinNetwork) - : bcoin.Address.fromPubkeyhash(buffer).toString(bcoinNetwork) + ? payments.p2wpkh({ hash, network }).address! + : payments.p2pkh({ hash, network }).address! } /** * Converts a P2PKH or P2WPKH address into a public key hash. Throws if the * provided address is not PKH-based. - * @param address - P2PKH or P2WPKH address that will be decoded. + * @param address P2PKH or P2WPKH address that will be decoded. + * @param bitcoinNetwork Network the address should be decoded for. * @returns Public key hash decoded from the address. This will be an unprefixed * hex string (without 0x prefix). */ -function addressToPublicKeyHash(address: string): string { - const addressObject = new bcoin.Address(address) +function addressToPublicKeyHash( + address: string, + bitcoinNetwork: BitcoinNetwork +): string { + const network = toBitcoinJsLibNetwork(bitcoinNetwork) - const isPKH = - addressObject.isPubkeyhash() || addressObject.isWitnessPubkeyhash() - if (!isPKH) { - throw new Error("Address must be P2PKH or P2WPKH") - } + try { + // Try extracting hash from P2PKH address. + const hash = payments.p2pkh({ address: address, network }).hash! + return hash.toString("hex") + } catch (err) {} + + try { + // Try extracting hash from P2WPKH address. + const hash = payments.p2wpkh({ address: address, network }).hash! + return hash.toString("hex") + } catch (err) {} - return addressObject.getHash("hex") + throw new Error("Address must be P2PKH or P2WPKH valid for given network") } /** * Converts an address to the respective output script. * @param address BTC address. + * @param bitcoinNetwork Bitcoin network corresponding to the address. * @returns The un-prefixed and not prepended with length output script. */ -function addressToOutputScript(address: string): Hex { - return Hex.from(Script.fromAddress(address).toRaw().toString("hex")) +function addressToOutputScript( + address: string, + bitcoinNetwork: BitcoinNetwork +): Hex { + return Hex.from( + btcjsaddress.toOutputScript(address, toBitcoinJsLibNetwork(bitcoinNetwork)) + ) } /** * Converts an output script to the respective network-specific address. * @param script The unprefixed and not prepended with length output script. - * @param network Bitcoin network. + * @param bitcoinNetwork Bitcoin network the address should be produced for. * @returns The Bitcoin address. */ function outputScriptToAddress( script: Hex, - network: BitcoinNetwork = BitcoinNetwork.Mainnet + bitcoinNetwork: BitcoinNetwork = BitcoinNetwork.Mainnet ): string { - return Script.fromRaw(script.toString(), "hex") - .getAddress() - ?.toString(toBcoinNetwork(network)) + return btcjsaddress.fromOutputScript( + script.toBuffer(), + toBitcoinJsLibNetwork(bitcoinNetwork) + ) } /** diff --git a/typescript/src/lib/bitcoin/network.ts b/typescript/src/lib/bitcoin/network.ts index c40afb3bf..bf2376674 100644 --- a/typescript/src/lib/bitcoin/network.ts +++ b/typescript/src/lib/bitcoin/network.ts @@ -45,27 +45,6 @@ export namespace BitcoinNetwork { } } -/** - * Converts enumerated {@link BitcoinNetwork} to a string expected by the - * {@link https://github.com/keep-network/bcoin/blob/aba6841e43546e8a485e96dc0019d1e788eab2ee/lib/protocol/networks.js#L33| `bcoin` library} - * @param bitcoinNetwork Bitcoin network. - * @returns String representing the given network in bcoin library. - * @throws An error if the network is not supported by bcoin. - */ -export function toBcoinNetwork(bitcoinNetwork: BitcoinNetwork): string { - switch (bitcoinNetwork) { - case BitcoinNetwork.Mainnet: { - return "main" - } - case BitcoinNetwork.Testnet: { - return "testnet" - } - default: { - throw new Error(`network not supported`) - } - } -} - /** * Converts the provided {@link BitcoinNetwork} enumeration to a format expected * by the `bitcoinjs-lib` library. diff --git a/typescript/src/lib/bitcoin/tx.ts b/typescript/src/lib/bitcoin/tx.ts index 6841f4225..f068500b1 100644 --- a/typescript/src/lib/bitcoin/tx.ts +++ b/typescript/src/lib/bitcoin/tx.ts @@ -1,4 +1,4 @@ -import { TX } from "bcoin" +import { Transaction as Tx } from "bitcoinjs-lib" import bufio from "bufio" import { BigNumber } from "ethers" import { Hex } from "../utils" @@ -133,46 +133,53 @@ export interface BitcoinRawTxVectors { export function extractBitcoinRawTxVectors( rawTransaction: BitcoinRawTx ): BitcoinRawTxVectors { - const toHex = (bufferWriter: any) => { + const toHex = (bufferWriter: any): string => { return bufferWriter.render().toString("hex") } - const vectorToRaw = (elements: any[]) => { + const getTxInputVector = (tx: Tx): string => { const buffer = bufio.write() - buffer.writeVarint(elements.length) - for (const element of elements) { - element.toWriter(buffer) - } + buffer.writeVarint(tx.ins.length) + tx.ins.forEach((input) => { + buffer.writeHash(input.hash) + buffer.writeU32(input.index) + buffer.writeVarBytes(input.script) + buffer.writeU32(input.sequence) + }) return toHex(buffer) } - const getTxInputVector = (tx: any) => { - return vectorToRaw(tx.inputs) - } - - const getTxOutputVector = (tx: any) => { - return vectorToRaw(tx.outputs) + const getTxOutputVector = (tx: Tx): string => { + const buffer = bufio.write() + buffer.writeVarint(tx.outs.length) + tx.outs.forEach((output) => { + buffer.writeI64(output.value) + buffer.writeVarBytes(output.script) + }) + return toHex(buffer) } - const getTxVersion = (tx: any) => { + const getTxVersion = (tx: Tx): string => { const buffer = bufio.write() buffer.writeU32(tx.version) return toHex(buffer) } - const getTxLocktime = (tx: any) => { + const getTxLocktime = (tx: Tx): string => { const buffer = bufio.write() buffer.writeU32(tx.locktime) return toHex(buffer) } - const tx = TX.fromRaw(Buffer.from(rawTransaction.transactionHex, "hex"), null) + const transaction = Tx.fromBuffer( + Buffer.from(rawTransaction.transactionHex, "hex") + ) return { - version: getTxVersion(tx), - inputs: getTxInputVector(tx), - outputs: getTxOutputVector(tx), - locktime: getTxLocktime(tx), + version: getTxVersion(transaction), + inputs: getTxInputVector(transaction), + outputs: getTxOutputVector(transaction), + locktime: getTxLocktime(transaction), } } diff --git a/typescript/src/lib/electrum/client.ts b/typescript/src/lib/electrum/client.ts index dd0b10aab..d4dbb57c8 100644 --- a/typescript/src/lib/electrum/client.ts +++ b/typescript/src/lib/electrum/client.ts @@ -1,4 +1,4 @@ -import bcoin from "bcoin" +import { Transaction as Tx, TxInput, TxOutput } from "bitcoinjs-lib" import pTimeout from "p-timeout" import { BitcoinClient, @@ -11,6 +11,7 @@ import { BitcoinTxMerkleBranch, BitcoinTxOutput, BitcoinUtxo, + BitcoinHashUtils, } from "../bitcoin" import Electrum from "electrum-client-js" import { BigNumber, utils } from "ethers" @@ -255,8 +256,12 @@ export class ElectrumClient implements BitcoinClient { */ findAllUnspentTransactionOutputs(address: string): Promise { return this.withElectrum(async (electrum: Electrum) => { - const script = - BitcoinAddressConverter.addressToOutputScript(address).toString() + const bitcoinNetwork = await this.getNetwork() + + const script = BitcoinAddressConverter.addressToOutputScript( + address, + bitcoinNetwork + ).toString() // eslint-disable-next-line camelcase type UnspentOutput = { tx_pos: number; value: number; tx_hash: string } @@ -282,8 +287,12 @@ export class ElectrumClient implements BitcoinClient { */ getTransactionHistory(address: string, limit?: number): Promise { return this.withElectrum(async (electrum: Electrum) => { - const script = - BitcoinAddressConverter.addressToOutputScript(address).toString() + const bitcoinNetwork = await this.getNetwork() + + const script = BitcoinAddressConverter.addressToOutputScript( + address, + bitcoinNetwork + ).toString() // eslint-disable-next-line camelcase type HistoryItem = { height: number; tx_hash: string } @@ -350,26 +359,26 @@ export class ElectrumClient implements BitcoinClient { } // Decode the raw transaction. - const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") + const transaction = Tx.fromHex(rawTransaction) - const inputs = transaction.inputs.map( - (input: any): BitcoinTxInput => ({ - transactionHash: BitcoinTxHash.from(input.prevout.hash).reverse(), - outputIndex: input.prevout.index, - scriptSig: Hex.from(input.script.toRaw()), + const inputs = transaction.ins.map( + (input: TxInput): BitcoinTxInput => ({ + transactionHash: BitcoinTxHash.from(input.hash).reverse(), + outputIndex: input.index, + scriptSig: Hex.from(input.script), }) ) - const outputs = transaction.outputs.map( - (output: any, i: number): BitcoinTxOutput => ({ + const outputs = transaction.outs.map( + (output: TxOutput, i: number): BitcoinTxOutput => ({ outputIndex: i, value: BigNumber.from(output.value), - scriptPubKey: Hex.from(output.script.toRaw()), + scriptPubKey: Hex.from(output.script), }) ) return { - transactionHash: BitcoinTxHash.from(transaction.hash()).reverse(), + transactionHash: BitcoinTxHash.from(transaction.getId()), inputs: inputs, outputs: outputs, } @@ -418,7 +427,7 @@ export class ElectrumClient implements BitcoinClient { ) // Decode the raw transaction. - const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") + const transaction = Tx.fromHex(rawTransaction) // As a workaround for the problem described in https://github.com/Blockstream/electrs/pull/36 // we need to calculate the number of confirmations based on the latest @@ -436,8 +445,10 @@ export class ElectrumClient implements BitcoinClient { // If a transaction is unconfirmed (is still in the mempool) the height will // have a value of `0` or `-1`. let txBlockHeight: number = Math.min() - for (const output of transaction.outputs) { - const scriptHash: Buffer = output.script.sha256() + for (const output of transaction.outs) { + const scriptHash: Buffer = BitcoinHashUtils.computeSha256( + Hex.from(output.script) + ).toBuffer() type HistoryEntry = { // eslint-disable-next-line camelcase diff --git a/typescript/src/services/deposits/deposits-service.ts b/typescript/src/services/deposits/deposits-service.ts index eb985d7c1..e04048d29 100644 --- a/typescript/src/services/deposits/deposits-service.ts +++ b/typescript/src/services/deposits/deposits-service.ts @@ -77,11 +77,14 @@ export class DepositsService { const walletPublicKeyHash = BitcoinHashUtils.computeHash160(walletPublicKey) + const bitcoinNetwork = await this.bitcoinClient.getNetwork() + // TODO: Only P2(W)PKH addresses can be used for recovery. The below conversion // function ensures that but, it would be good to check it here as well // in case the converter implementation changes. const refundPublicKeyHash = BitcoinAddressConverter.addressToPublicKeyHash( - bitcoinRecoveryAddress + bitcoinRecoveryAddress, + bitcoinNetwork ) const currentTimestamp = Math.floor(new Date().getTime() / 1000) diff --git a/typescript/src/services/deposits/refund.ts b/typescript/src/services/deposits/refund.ts index 5c4f0dcb4..d4ad401fb 100644 --- a/typescript/src/services/deposits/refund.ts +++ b/typescript/src/services/deposits/refund.ts @@ -133,8 +133,10 @@ export class DepositRefund { utxo.outputIndex ) - const outputScript = - BitcoinAddressConverter.addressToOutputScript(refunderAddress) + const outputScript = BitcoinAddressConverter.addressToOutputScript( + refunderAddress, + bitcoinNetwork + ) transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber()) // In order to be able to spend the UTXO being refunded the transaction's diff --git a/typescript/src/services/maintenance/wallet-tx.ts b/typescript/src/services/maintenance/wallet-tx.ts index 552929510..dda223671 100644 --- a/typescript/src/services/maintenance/wallet-tx.ts +++ b/typescript/src/services/maintenance/wallet-tx.ts @@ -216,8 +216,10 @@ class DepositSweep { } outputValue = outputValue.sub(fee) - const outputScript = - BitcoinAddressConverter.addressToOutputScript(walletAddress) + const outputScript = BitcoinAddressConverter.addressToOutputScript( + walletAddress, + bitcoinNetwork + ) transaction.addOutput(outputScript.toBuffer(), outputValue.toNumber()) diff --git a/typescript/src/services/redemptions/redemptions-service.ts b/typescript/src/services/redemptions/redemptions-service.ts index 2b99e7fbd..f3fd5f49c 100644 --- a/typescript/src/services/redemptions/redemptions-service.ts +++ b/typescript/src/services/redemptions/redemptions-service.ts @@ -51,8 +51,11 @@ export class RedemptionsService { targetChainTxHash: Hex walletPublicKey: string }> { + const bitcoinNetwork = await this.bitcoinClient.getNetwork() + const redeemerOutputScript = BitcoinAddressConverter.addressToOutputScript( - bitcoinRedeemerAddress + bitcoinRedeemerAddress, + bitcoinNetwork ).toString() // TODO: Validate the given script is supported for redemption. @@ -258,8 +261,10 @@ export class RedemptionsService { // Get the wallet script based on the wallet address. This is required // to find transaction outputs that lock funds on the wallet. - const walletScript = - BitcoinAddressConverter.addressToOutputScript(walletAddress) + const walletScript = BitcoinAddressConverter.addressToOutputScript( + walletAddress, + bitcoinNetwork + ) const isWalletOutput = (output: BitcoinTxOutput) => walletScript.equals(output.scriptPubKey) @@ -328,8 +333,11 @@ export class RedemptionsService { walletPublicKey: string, type: "pending" | "timedOut" = "pending" ): Promise { + const bitcoinNetwork = await this.bitcoinClient.getNetwork() + const redeemerOutputScript = BitcoinAddressConverter.addressToOutputScript( - bitcoinRedeemerAddress + bitcoinRedeemerAddress, + bitcoinNetwork ).toString() let redemptionRequest: RedemptionRequest | undefined = undefined diff --git a/typescript/test/bitcoin-network.test.ts b/typescript/test/bitcoin-network.test.ts index 32102f996..b8457f52a 100644 --- a/typescript/test/bitcoin-network.test.ts +++ b/typescript/test/bitcoin-network.test.ts @@ -1,10 +1,5 @@ import { expect } from "chai" -import { - BitcoinTxHash, - BitcoinNetwork, - toBcoinNetwork, - toBitcoinJsLibNetwork, -} from "../src" +import { BitcoinTxHash, BitcoinNetwork, toBitcoinJsLibNetwork } from "../src" import { networks } from "bitcoinjs-lib" describe("BitcoinNetwork", () => { @@ -14,7 +9,6 @@ describe("BitcoinNetwork", () => { enumValue: "unknown", // any value that doesn't match other supported networks genesisHash: BitcoinTxHash.from("0x00010203"), - expectedToBcoinResult: new Error("network not supported"), expectedToBitcoinJsLibResult: new Error("network not supported"), }, { @@ -23,7 +17,6 @@ describe("BitcoinNetwork", () => { genesisHash: BitcoinTxHash.from( "0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" ), - expectedToBcoinResult: "testnet", expectedToBitcoinJsLibResult: networks.testnet, }, { @@ -32,19 +25,12 @@ describe("BitcoinNetwork", () => { genesisHash: BitcoinTxHash.from( "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" ), - expectedToBcoinResult: "main", expectedToBitcoinJsLibResult: networks.bitcoin, }, ] testData.forEach( - ({ - enumKey, - enumValue, - genesisHash, - expectedToBcoinResult, - expectedToBitcoinJsLibResult, - }) => { + ({ enumKey, enumValue, genesisHash, expectedToBitcoinJsLibResult }) => { context(enumKey, async () => { describe(`toString`, async () => { it(`should return correct value`, async () => { @@ -60,20 +46,6 @@ describe("BitcoinNetwork", () => { }) }) - describe(`toBcoinNetwork`, async () => { - if (expectedToBcoinResult instanceof Error) { - it(`should throw an error`, async () => { - expect(() => toBcoinNetwork(enumKey)).to.throw( - expectedToBcoinResult.message - ) - }) - } else { - it(`should return ${expectedToBcoinResult}`, async () => { - expect(toBcoinNetwork(enumKey)).to.be.equal(expectedToBcoinResult) - }) - } - }) - describe(`toBitcoinJsLibNetwork`, async () => { if (expectedToBitcoinJsLibResult instanceof Error) { it(`should throw an error`, async () => { diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/bitcoin.test.ts index c2d1bb007..6b5b2f206 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/bitcoin.test.ts @@ -1,19 +1,21 @@ import { expect } from "chai" import { - BitcoinNetwork, - BitcoinPublicKeyUtils, - BitcoinLocktimeUtils, + BitcoinAddressConverter, + BitcoinCompactSizeUint, + BitcoinHashUtils, BitcoinHeader, BitcoinHeaderSerializer, - BitcoinHashUtils, + BitcoinLocktimeUtils, + BitcoinNetwork, + BitcoinPublicKeyUtils, + BitcoinScriptUtils, BitcoinTargetConverter, - BitcoinCompactSizeUint, - BitcoinAddressConverter, + extractBitcoinRawTxVectors, Hex, - BitcoinScriptUtils, } from "../src" import { BigNumber } from "ethers" import { btcAddresses, btcAddressFromPublicKey } from "./data/bitcoin" +import { depositSweepWithNoMainUtxoAndWitnessOutput } from "./data/deposit-sweep" describe("Bitcoin", () => { describe("BitcoinPublicKeyUtils", () => { @@ -185,7 +187,9 @@ describe("Bitcoin", () => { true, BitcoinNetwork.Mainnet ) - ).to.throw("P2WPKH must be 20 bytes") + ).to.throw( + `Expected property "hash" of type Buffer(Length: 20), got Buffer(Length: 21)` + ) }) }) }) @@ -213,7 +217,9 @@ describe("Bitcoin", () => { false, BitcoinNetwork.Mainnet ) - ).to.throw("P2PKH must be 20 bytes") + ).to.throw( + `Expected property "hash" of type Buffer(Length: 20), got Buffer(Length: 21)` + ) }) }) }) @@ -243,7 +249,9 @@ describe("Bitcoin", () => { true, BitcoinNetwork.Testnet ) - ).to.throw("P2WPKH must be 20 bytes") + ).to.throw( + `Expected property "hash" of type Buffer(Length: 20), got Buffer(Length: 21)` + ) }) }) }) @@ -271,7 +279,9 @@ describe("Bitcoin", () => { false, BitcoinNetwork.Testnet ) - ).to.throw("P2PKH must be 20 bytes") + ).to.throw( + `Expected property "hash" of type Buffer(Length: 20), got Buffer(Length: 21)` + ) }) }) }) @@ -290,17 +300,17 @@ describe("Bitcoin", () => { context("when network is mainnet", () => { context("when proper P2WPKH address is provided", () => { it("should decode P2WPKH adress correctly", () => { - expect(addressToPublicKeyHash(P2WPKHAddress)).to.be.equal( - publicKeyHash - ) + expect( + addressToPublicKeyHash(P2WPKHAddress, BitcoinNetwork.Mainnet) + ).to.be.equal(publicKeyHash) }) }) context("when proper P2PKH address is provided", () => { it("should decode P2PKH address correctly", () => { - expect(addressToPublicKeyHash(P2PKHAddress)).to.be.equal( - publicKeyHash - ) + expect( + addressToPublicKeyHash(P2PKHAddress, BitcoinNetwork.Mainnet) + ).to.be.equal(publicKeyHash) }) }) @@ -308,8 +318,10 @@ describe("Bitcoin", () => { it("should throw", () => { const bitcoinAddress = "123" + P2PKHAddress - expect(() => addressToPublicKeyHash(bitcoinAddress)).to.throw( - "Address is too long" + expect(() => + addressToPublicKeyHash(bitcoinAddress, BitcoinNetwork.Mainnet) + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" ) }) }) @@ -317,8 +329,13 @@ describe("Bitcoin", () => { context("when unsupported P2SH address is provided", () => { it("should throw", () => { expect(() => - addressToPublicKeyHash("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX") - ).to.throw("Address must be P2PKH or P2WPKH") + addressToPublicKeyHash( + "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", + BitcoinNetwork.Mainnet + ) + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" + ) }) }) @@ -326,9 +343,25 @@ describe("Bitcoin", () => { it("should throw", () => { expect(() => addressToPublicKeyHash( - "bc1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhsdxuv4m" + "bc1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhsdxuv4m", + BitcoinNetwork.Mainnet + ) + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" + ) + }) + }) + + context("when address from testnet network is provided", () => { + it("should throw", () => { + expect(() => + addressToPublicKeyHash( + "mkpoZkRvtd3SDGWgUDuXK1aEXZfHRM2gKw", + BitcoinNetwork.Mainnet ) - ).to.throw("Address must be P2PKH or P2WPKH") + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" + ) }) }) }) @@ -336,17 +369,23 @@ describe("Bitcoin", () => { context("when network is testnet", () => { context("when proper P2WPKH address is provided", () => { it("should decode P2WPKH adress correctly", () => { - expect(addressToPublicKeyHash(P2WPKHAddressTestnet)).to.be.equal( - publicKeyHash - ) + expect( + addressToPublicKeyHash( + P2WPKHAddressTestnet, + BitcoinNetwork.Testnet + ) + ).to.be.equal(publicKeyHash) }) }) context("when proper P2PKH address is provided", () => { it("should decode P2PKH address correctly", () => { - expect(addressToPublicKeyHash(P2PKHAddressTestnet)).to.be.equal( - publicKeyHash - ) + expect( + addressToPublicKeyHash( + P2PKHAddressTestnet, + BitcoinNetwork.Testnet + ) + ).to.be.equal(publicKeyHash) }) }) @@ -354,8 +393,10 @@ describe("Bitcoin", () => { it("should throw", () => { const bitcoinAddress = "123" + P2PKHAddressTestnet - expect(() => addressToPublicKeyHash(bitcoinAddress)).to.throw( - "Address is too long" + expect(() => + addressToPublicKeyHash(bitcoinAddress, BitcoinNetwork.Testnet) + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" ) }) }) @@ -363,8 +404,13 @@ describe("Bitcoin", () => { context("when unsupported P2SH address is provided", () => { it("should throw", () => { expect(() => - addressToPublicKeyHash("2MyxShnGQ5NifGb8CHYrtmzosRySxZ9pZo5") - ).to.throw("Address must be P2PKH or P2WPKH") + addressToPublicKeyHash( + "2MyxShnGQ5NifGb8CHYrtmzosRySxZ9pZo5", + BitcoinNetwork.Testnet + ) + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" + ) }) }) @@ -372,9 +418,25 @@ describe("Bitcoin", () => { it("should throw", () => { expect(() => addressToPublicKeyHash( - "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05" + "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05", + BitcoinNetwork.Testnet ) - ).to.throw("Address must be P2PKH or P2WPKH") + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" + ) + }) + }) + + context("when address from mainnet network is provided", () => { + it("should throw", () => { + expect(() => + addressToPublicKeyHash( + "bc1q8gudgnt2pjxshwzwqgevccet0eyvwtswt03nuy", + BitcoinNetwork.Testnet + ) + ).to.throw( + "Address must be P2PKH or P2WPKH valid for given network" + ) }) }) }) @@ -391,7 +453,12 @@ describe("Bitcoin", () => { { address, scriptPubKey: expectedOutputScript }, ]) => { it(`should create correct output script for ${addressType} address type`, () => { - const result = addressToOutputScript(address) + const network = + bitcoinNetwork === "mainnet" + ? BitcoinNetwork.Mainnet + : BitcoinNetwork.Testnet + + const result = addressToOutputScript(address, network) expect(result.toString()).to.eq(expectedOutputScript.toString()) }) @@ -711,4 +778,29 @@ describe("Bitcoin", () => { }) }) }) + + describe("extractBitcoinRawTxVectors", () => { + it("should return correct transaction vectors", () => { + const rawTransaction = + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transaction + const decomposedTransaction = extractBitcoinRawTxVectors(rawTransaction) + + expect(decomposedTransaction.version).to.be.equal("01000000") + expect(decomposedTransaction.inputs).to.be.equal( + "02bc187be612bc3db8cfcdec56b75e9bc0262ab6eacfe27cc1a699bacd53e3d07400" + + "000000c948304502210089a89aaf3fec97ac9ffa91cdff59829f0cb3ef852a468153" + + "e2c0e2b473466d2e022072902bb923ef016ac52e941ced78f816bf27991c2b73211e" + + "227db27ec200bc0a012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f25" + + "64da4cc29dcf8581d94c5c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508" + + "f9f0c90d000395237576a9148db50eb52063ea9d98b3eac91489a90f738986f68763" + + "ac6776a914e257eccafbc07c381642ce6e7e55120fb077fbed8804e0250162b175ac" + + "68ffffffffdc557e737b6688c5712649b86f7757a722dc3d42786f23b2fa826394df" + + "ec545c0000000000ffffffff" + ) + expect(decomposedTransaction.outputs).to.be.equal( + "01488a0000000000001600148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + expect(decomposedTransaction.locktime).to.be.equal("00000000") + }) + }) }) diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index 9ff50845d..f840e2b6a 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -1,17 +1,18 @@ import { BigNumber, BytesLike } from "ethers" import { + BitcoinAddressConverter, + BitcoinNetwork, + BitcoinRawTx, BitcoinRawTxVectors, BitcoinSpvProof, BitcoinTx, - BitcoinRawTx, - BitcoinUtxo, - BitcoinTxMerkleBranch, BitcoinTxHash, - BitcoinAddressConverter, - RedemptionRequest, - WalletState, + BitcoinTxMerkleBranch, + BitcoinUtxo, EthereumAddress, Hex, + RedemptionRequest, + WalletState, } from "../../src" /** @@ -745,7 +746,8 @@ export const findWalletForRedemptionData: { outputIndex: 0, value: BigNumber.from("791613461"), scriptPubKey: BitcoinAddressConverter.addressToOutputScript( - "tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja" + "tb1qqwm566yn44rdlhgph8sw8vecta8uutg79afuja", + BitcoinNetwork.Testnet ), }, ], @@ -870,7 +872,8 @@ export const findWalletForRedemptionData: { outputIndex: 0, value: BigNumber.from("3370000"), // 0.0337 BTC scriptPubKey: BitcoinAddressConverter.addressToOutputScript( - "tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh" + "tb1qx2xejtjltdcau5dpks8ucszkhxdg3fj88404lh", + BitcoinNetwork.Testnet ), }, ], diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts index 10aa2b640..54965e208 100644 --- a/typescript/test/deposit-sweep.test.ts +++ b/typescript/test/deposit-sweep.test.ts @@ -8,12 +8,7 @@ import { MaintenanceService, WalletTx, } from "../src" -import { - testnetDepositScripthashAddress, - testnetDepositWitnessScripthashAddress, - testnetWalletAddress, - testnetWalletPrivateKey, -} from "./data/deposit" +import { testnetWalletAddress, testnetWalletPrivateKey } from "./data/deposit" import { depositSweepWithWitnessMainUtxoAndWitnessOutput, depositSweepWithNoMainUtxoAndWitnessOutput, @@ -23,12 +18,12 @@ import { NO_MAIN_UTXO, } from "./data/deposit-sweep" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import bcoin from "bcoin" import * as chai from "chai" import chaiAsPromised from "chai-as-promised" chai.use(chaiAsPromised) import { expect } from "chai" import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" +import { txToJSON } from "./utils/helpers" describe("Sweep", () => { const fee = BigNumber.from(1600) @@ -445,8 +440,10 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() @@ -457,28 +454,23 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(2) const p2shInput = txJSON.inputs[0] - expect(p2shInput.prevout.hash).to.be.equal( + expect(p2shInput.hash).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2shInput.prevout.index).to.be.equal( + expect(p2shInput.index).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.equal("00") + expect(p2shInput.witness).to.be.empty expect(p2shInput.script.length).to.be.greaterThan(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2shInput.address).to.be.equal( - testnetDepositScripthashAddress - ) const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.prevout.hash).to.be.equal( + expect(p2wshInput.hash).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() ) - expect(p2wshInput.prevout.index).to.be.equal( + expect(p2wshInput.index).to.be.equal( depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo .outputIndex ) @@ -486,11 +478,6 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wshInput.witness.length).to.be.greaterThan(0) expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // witness script hash - expect(p2wshInput.address).to.be.equal( - testnetDepositWitnessScripthashAddress - ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -587,8 +574,10 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() @@ -599,10 +588,10 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(3) const p2wkhInput = txJSON.inputs[0] - expect(p2wkhInput.prevout.hash).to.be.equal( + expect(p2wkhInput.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() ) - expect(p2wkhInput.prevout.index).to.be.equal( + expect(p2wkhInput.index).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo .outputIndex ) @@ -610,33 +599,25 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wkhInput.witness.length).to.be.greaterThan(0) expect(p2wkhInput.script.length).to.be.equal(0) - // The input comes from the main UTXO so the input should be the - // wallet's address - expect(p2wkhInput.address).to.be.equal(testnetWalletAddress) const p2shInput = txJSON.inputs[1] - expect(p2shInput.prevout.hash).to.be.equal( + expect(p2shInput.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2shInput.prevout.index).to.be.equal( + expect(p2shInput.index).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0] .utxo.outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.equal("00") + expect(p2shInput.witness).to.be.empty expect(p2shInput.script.length).to.be.greaterThan(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2shInput.address).to.be.equal( - testnetDepositScripthashAddress - ) const p2wshInput = txJSON.inputs[2] - expect(p2wshInput.prevout.hash).to.be.equal( + expect(p2wshInput.hash).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() ) - expect(p2wshInput.prevout.index).to.be.equal( + expect(p2wshInput.index).to.be.equal( depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1] .utxo.outputIndex ) @@ -644,11 +625,6 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wshInput.witness.length).to.be.greaterThan(0) expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // witness script hash - expect(p2wshInput.address).to.be.equal( - testnetDepositWitnessScripthashAddress - ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -744,8 +720,10 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() @@ -756,28 +734,23 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(2) const p2pkhInput = txJSON.inputs[0] // main UTXO - expect(p2pkhInput.prevout.hash).to.be.equal( + expect(p2pkhInput.hash).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() ) - expect(p2pkhInput.prevout.index).to.be.equal( + expect(p2pkhInput.index).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2pkhInput.witness).to.be.equal("00") + expect(p2pkhInput.witness).to.be.empty expect(p2pkhInput.script.length).to.be.greaterThan(0) - // The input comes from the main UTXO so the input should be the - // wallet's address - expect(p2pkhInput.address).to.be.equal( - "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" - ) const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.prevout.hash).to.be.equal( + expect(p2wshInput.hash).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2wshInput.prevout.index).to.be.equal( + expect(p2wshInput.index).to.be.equal( depositSweepWithNonWitnessMainUtxoAndWitnessOutput .deposits[0].utxo.outputIndex ) @@ -785,11 +758,6 @@ describe("Sweep", () => { // field should be filled, while the `script` field should be empty. expect(p2wshInput.witness.length).to.be.greaterThan(0) expect(p2wshInput.script.length).to.be.equal(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2wshInput.address).to.be.equal( - "tb1qk8urugnf08wfle6wslmdxq7mkz9z0gw8e6gkvspn7dx87tfpfntshdm7qr" - ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) @@ -878,8 +846,10 @@ describe("Sweep", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep.transactionHash.toString() @@ -890,22 +860,17 @@ describe("Sweep", () => { expect(txJSON.inputs.length).to.be.equal(1) const p2shInput = txJSON.inputs[0] - expect(p2shInput.prevout.hash).to.be.equal( + expect(p2shInput.hash).to.be.equal( depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo.transactionHash.toString() ) - expect(p2shInput.prevout.index).to.be.equal( + expect(p2shInput.index).to.be.equal( depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo .outputIndex ) // Transaction should be signed. As it's not SegWit input, the `witness` // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.equal("00") + expect(p2shInput.witness).to.be.empty expect(p2shInput.script.length).to.be.greaterThan(0) - // Input's address should be set to the address generated from deposit - // script hash - expect(p2shInput.address).to.be.equal( - "2N8iF1pRndihBzgLDna9MfRhmqktwTdHejA" - ) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(1) diff --git a/typescript/test/deposit.test.ts b/typescript/test/deposit.test.ts index ec135e568..73f33e320 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/deposit.test.ts @@ -21,8 +21,8 @@ import { extractBitcoinRawTxVectors, } from "../src" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import bcoin from "bcoin" import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" +import { txToJSON } from "./utils/helpers" describe("Deposit", () => { const depositCreatedAt: number = 1640181600 @@ -343,8 +343,10 @@ describe("Deposit", () => { expect(transaction).to.be.eql(expectedP2WSHDeposit.transaction) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( expectedP2WSHDeposit.transactionHash.toString() @@ -356,15 +358,12 @@ describe("Deposit", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( - testnetUTXO.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) + expect(input.hash).to.be.equal(testnetUTXO.transactionHash.toString()) + expect(input.index).to.be.equal(testnetUTXO.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(testnetAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -447,8 +446,10 @@ describe("Deposit", () => { expect(transaction).to.be.eql(expectedP2SHDeposit.transaction) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( expectedP2SHDeposit.transactionHash.toString() @@ -460,15 +461,12 @@ describe("Deposit", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( - testnetUTXO.transactionHash.toString() - ) - expect(input.prevout.index).to.be.equal(testnetUTXO.outputIndex) + expect(input.hash).to.be.equal(testnetUTXO.transactionHash.toString()) + expect(input.index).to.be.equal(testnetUTXO.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(testnetAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) diff --git a/typescript/test/redemption.test.ts b/typescript/test/redemption.test.ts index 371662f2c..4fbd9f5fe 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/redemption.test.ts @@ -6,15 +6,15 @@ import { BitcoinTx, BitcoinTxHash, BitcoinUtxo, + Hex, + MaintenanceService, NewWalletRegisteredEvent, RedemptionRequest, + RedemptionsService, Wallet, WalletState, - RedemptionsService, WalletTx, - MaintenanceService, } from "../src" -import bcoin from "bcoin" import { MockBitcoinClient } from "./utils/mock-bitcoin-client" import { findWalletForRedemptionData, @@ -38,7 +38,7 @@ import { expect } from "chai" import chaiAsPromised from "chai-as-promised" import { BigNumber, BigNumberish } from "ethers" import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" -import { Hex } from "../src" +import { txToJSON } from "./utils/helpers" chai.use(chaiAsPromised) @@ -99,8 +99,10 @@ describe("Redemption", () => { { outputIndex: mainUtxo.outputIndex, value: mainUtxo.value, - scriptPubKey: - BitcoinAddressConverter.addressToOutputScript(walletAddress), + scriptPubKey: BitcoinAddressConverter.addressToOutputScript( + walletAddress, + BitcoinNetwork.Testnet + ), }, ], } as BitcoinTx, @@ -1282,11 +1284,10 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from( + const txJSON = txToJSON( transaction.transactionHex, - "hex" + BitcoinNetwork.Testnet ) - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1298,17 +1299,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -1407,11 +1405,10 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from( + const txJSON = txToJSON( transaction.transactionHex, - "hex" + BitcoinNetwork.Testnet ) - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1423,17 +1420,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -1531,11 +1525,10 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from( + const txJSON = txToJSON( transaction.transactionHex, - "hex" + BitcoinNetwork.Testnet ) - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1547,17 +1540,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -1655,11 +1645,10 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from( + const txJSON = txToJSON( transaction.transactionHex, - "hex" + BitcoinNetwork.Testnet ) - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1671,17 +1660,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -1778,8 +1764,10 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1791,17 +1779,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(5) @@ -1945,8 +1930,10 @@ describe("Redemption", () => { ) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -1958,17 +1945,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal( - data.mainUtxo.outputIndex - ) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) @@ -2059,8 +2043,10 @@ describe("Redemption", () => { expect(transaction).to.be.eql(data.expectedRedemption.transaction) // Convert raw transaction to JSON to make detailed comparison. - const buffer = Buffer.from(transaction.transactionHex, "hex") - const txJSON = bcoin.TX.fromRaw(buffer).getJSON("testnet") + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) expect(txJSON.hash).to.be.equal( data.expectedRedemption.transactionHash.toString() @@ -2072,15 +2058,14 @@ describe("Redemption", () => { const input = txJSON.inputs[0] - expect(input.prevout.hash).to.be.equal( + expect(input.hash).to.be.equal( data.mainUtxo.transactionHash.toString() ) - expect(input.prevout.index).to.be.equal(data.mainUtxo.outputIndex) + expect(input.index).to.be.equal(data.mainUtxo.outputIndex) // Transaction should be signed but this is SegWit input so the `script` // field should be empty and the `witness` field should be filled instead. expect(input.script.length).to.be.equal(0) expect(input.witness.length).to.be.greaterThan(0) - expect(input.address).to.be.equal(p2wpkhWalletAddress) // Validate outputs. expect(txJSON.outputs.length).to.be.equal(2) diff --git a/typescript/test/utils/helpers.ts b/typescript/test/utils/helpers.ts index 59634d35b..8378ea344 100644 --- a/typescript/test/utils/helpers.ts +++ b/typescript/test/utils/helpers.ts @@ -1,8 +1,4 @@ -import { Hex } from "../../src/hex" -import { - BitcoinNetwork, - toBitcoinJsLibNetwork, -} from "../../src/bitcoin-network" +import { BitcoinNetwork, toBitcoinJsLibNetwork, Hex } from "../../src" import { Transaction, address } from "bitcoinjs-lib" /** diff --git a/typescript/yarn.lock b/typescript/yarn.lock index 05897068f..ba01c6262 100644 --- a/typescript/yarn.lock +++ b/typescript/yarn.lock @@ -2496,45 +2496,6 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -"bcfg@git+https://github.com/bcoin-org/bcfg.git#semver:~0.1.7": - version "0.1.7" - resolved "git+https://github.com/bcoin-org/bcfg.git#05122154b35baa82cd01dc9478ebee7346386ba1" - dependencies: - bsert "~0.0.10" - -"bcoin@git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8": - version "2.2.0" - resolved "git+https://github.com/keep-network/bcoin.git#5accd32c63e6025a0d35d67739c4a6e84095a1f8" - dependencies: - bcfg "git+https://github.com/bcoin-org/bcfg.git#semver:~0.1.7" - bcrypto "git+https://github.com/bcoin-org/bcrypto.git#semver:~5.5.0" - bcurl "git+https://github.com/bcoin-org/bcurl.git#semver:^0.1.6" - bdb "git+https://github.com/bcoin-org/bdb.git#semver:~1.2.1" - bdns "git+https://github.com/bcoin-org/bdns.git#semver:~0.1.5" - bevent "git+https://github.com/bcoin-org/bevent.git#semver:~0.1.5" - bfile "git+https://github.com/bcoin-org/bfile.git#semver:~0.2.1" - bfilter "git+https://github.com/bcoin-org/bfilter.git#semver:~2.3.0" - bheep "git+https://github.com/bcoin-org/bheep.git#semver:~0.1.5" - binet "git+https://github.com/bcoin-org/binet.git#semver:~0.3.5" - blgr "git+https://github.com/bcoin-org/blgr.git#semver:~0.2.0" - blru "git+https://github.com/bcoin-org/blru.git#semver:~0.1.6" - blst "git+https://github.com/bcoin-org/blst.git#semver:~0.1.5" - bmutex "git+https://github.com/bcoin-org/bmutex.git#semver:~0.1.6" - brq "git+https://github.com/bcoin-org/brq.git#semver:~0.1.7" - bs32 "git+https://github.com/bcoin-org/bs32.git#semver:=0.1.6" - bsert "git+https://github.com/chjj/bsert.git#semver:~0.0.10" - bsock "git+https://github.com/bcoin-org/bsock.git#semver:~0.1.9" - bsocks "git+https://github.com/bcoin-org/bsocks.git#semver:~0.2.6" - btcp "git+https://github.com/bcoin-org/btcp.git#semver:~0.1.5" - buffer-map "git+https://github.com/chjj/buffer-map.git#semver:~0.0.7" - bufio "git+https://github.com/bcoin-org/bufio.git#semver:~1.0.6" - bupnp "git+https://github.com/bcoin-org/bupnp.git#semver:~0.2.6" - bval "git+https://github.com/bcoin-org/bval.git#semver:~0.1.6" - bweb "git+https://github.com/bcoin-org/bweb.git#semver:=0.1.9" - loady "git+https://github.com/chjj/loady.git#semver:~0.0.1" - n64 "git+https://github.com/chjj/n64.git#semver:~0.2.10" - nan "git+https://github.com/braydonf/nan.git#semver:=2.14.0" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -2542,34 +2503,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -"bcrypto@git+https://github.com/bcoin-org/bcrypto.git#semver:~5.5.0": - version "5.5.0" - resolved "git+https://github.com/bcoin-org/bcrypto.git#34738cf15033e3bce91a4f6f41ec1ebee3c2fdc8" - dependencies: - bufio "~1.0.7" - loady "~0.0.5" - -"bcurl@git+https://github.com/bcoin-org/bcurl.git#semver:^0.1.6": - version "0.1.10" - resolved "git+https://github.com/bcoin-org/bcurl.git#d7e088fad4c284fb5d6fd7205c6b903bd3e6bf83" - dependencies: - brq "~0.1.8" - bsert "~0.0.10" - bsock "~0.1.9" - -"bdb@git+https://github.com/bcoin-org/bdb.git#semver:~1.2.1": - version "1.2.2" - resolved "git+https://github.com/bcoin-org/bdb.git#2c8d48c8adca4b11260263472766cd4b7ae74ef7" - dependencies: - bsert "~0.0.10" - loady "~0.0.1" - -"bdns@git+https://github.com/bcoin-org/bdns.git#semver:~0.1.5": - version "0.1.5" - resolved "git+https://github.com/bcoin-org/bdns.git#cb0b62a0075f7e1259fc50fa723ba644e9a07d14" - dependencies: - bsert "~0.0.10" - bech32@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" @@ -2580,31 +2513,6 @@ bech32@^2.0.0: resolved "https://registry.yarnpkg.com/bech32/-/bech32-2.0.0.tgz#078d3686535075c8c79709f054b1b226a133b355" integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== -"bevent@git+https://github.com/bcoin-org/bevent.git#semver:~0.1.5": - version "0.1.5" - resolved "git+https://github.com/bcoin-org/bevent.git#60fb503de3ea1292d29ce438bfba80f0bc5ccb60" - dependencies: - bsert "~0.0.10" - -"bfile@git+https://github.com/bcoin-org/bfile.git#semver:~0.2.1": - version "0.2.2" - resolved "git+https://github.com/bcoin-org/bfile.git#c3075133a02830dc384f8353d8275d4499b8bff9" - -"bfilter@git+https://github.com/bcoin-org/bfilter.git#semver:~2.3.0": - version "2.3.0" - resolved "git+https://github.com/bcoin-org/bfilter.git#70e42125f877191d340e8838a1a90fabb750e680" - dependencies: - bcrypto "git+https://github.com/bcoin-org/bcrypto.git#semver:~5.5.0" - bsert "git+https://github.com/chjj/bsert.git#semver:~0.0.10" - bufio "git+https://github.com/bcoin-org/bufio.git#semver:~1.0.6" - loady "git+https://github.com/chjj/loady.git#semver:~0.0.1" - -"bheep@git+https://github.com/bcoin-org/bheep.git#semver:~0.1.5": - version "0.1.5" - resolved "git+https://github.com/bcoin-org/bheep.git#e59329d0a776ae71b2fb7a2876ee5b9fd3030fa2" - dependencies: - bsert "~0.0.10" - big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -2637,13 +2545,6 @@ bindings@^1.3.0: dependencies: file-uri-to-path "1.0.0" -"binet@git+https://github.com/bcoin-org/binet.git#semver:~0.3.5", binet@~0.3.5: - version "0.3.6" - resolved "git+https://github.com/bcoin-org/binet.git#d3decfb7a7257abdfb03c3a9c091499b2ebff0e1" - dependencies: - bs32 "~0.1.5" - bsert "~0.0.10" - bip174@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" @@ -2718,18 +2619,6 @@ blakejs@^1.1.0: resolved "https://registry.yarnpkg.com/blakejs/-/blakejs-1.2.1.tgz#5057e4206eadb4a97f7c0b6e197a505042fc3814" integrity sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ== -"blgr@git+https://github.com/bcoin-org/blgr.git#semver:~0.2.0": - version "0.2.0" - resolved "git+https://github.com/bcoin-org/blgr.git#050cbb587a1654a078468dbb92606330fdc4d120" - dependencies: - bsert "~0.0.10" - -"blru@git+https://github.com/bcoin-org/blru.git#semver:~0.1.6": - version "0.1.6" - resolved "git+https://github.com/bcoin-org/blru.git#c2c093e9475439333dfb87bfb2fdc3be6c98b080" - dependencies: - bsert "~0.0.10" - "bls12377js@https://github.com/celo-org/bls12377js#400bcaeec9e7620b040bfad833268f5289699cac": version "0.1.0" resolved "https://github.com/celo-org/bls12377js#400bcaeec9e7620b040bfad833268f5289699cac" @@ -2754,23 +2643,11 @@ blakejs@^1.1.0: ts-node "^8.4.1" typescript "^3.6.4" -"blst@git+https://github.com/bcoin-org/blst.git#semver:~0.1.5": - version "0.1.5" - resolved "git+https://github.com/bcoin-org/blst.git#d588403edb18e628899e05aeba8c3a98a5cdedff" - dependencies: - bsert "~0.0.10" - bluebird@^3.5.0, bluebird@^3.5.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -"bmutex@git+https://github.com/bcoin-org/bmutex.git#semver:~0.1.6": - version "0.1.6" - resolved "git+https://github.com/bcoin-org/bmutex.git#e50782323932a4946ecc05a74c6d45861adc2c25" - dependencies: - bsert "~0.0.10" - bn.js@4.11.6: version "4.11.6" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" @@ -2893,18 +2770,6 @@ browserify-sign@^4.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -"brq@git+https://github.com/bcoin-org/brq.git#semver:~0.1.7", brq@~0.1.7, brq@~0.1.8: - version "0.1.8" - resolved "git+https://github.com/bcoin-org/brq.git#534bb2c83fb366ba40ad80bc3de796a174503294" - dependencies: - bsert "~0.0.10" - -"bs32@git+https://github.com/bcoin-org/bs32.git#semver:=0.1.6", bs32@~0.1.5: - version "0.1.6" - resolved "git+https://github.com/bcoin-org/bs32.git#21cf9c724659dc15df722d2410548828c142f265" - dependencies: - bsert "~0.0.10" - bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" @@ -2921,27 +2786,6 @@ bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: create-hash "^1.1.0" safe-buffer "^5.1.2" -"bsert@git+https://github.com/chjj/bsert.git#semver:~0.0.10", bsert@~0.0.10: - version "0.0.10" - resolved "git+https://github.com/chjj/bsert.git#bd09d49eab8644bca08ae8259a3d8756e7d453fc" - -"bsock@git+https://github.com/bcoin-org/bsock.git#semver:~0.1.9", bsock@~0.1.8, bsock@~0.1.9: - version "0.1.9" - resolved "git+https://github.com/bcoin-org/bsock.git#7cf76b3021ae7929c023d1170f789811e91ae528" - dependencies: - bsert "~0.0.10" - -"bsocks@git+https://github.com/bcoin-org/bsocks.git#semver:~0.2.6": - version "0.2.6" - resolved "git+https://github.com/bcoin-org/bsocks.git#6a8eb764dc4408e7f47da4f84e1afb1b393117e8" - dependencies: - binet "~0.3.5" - bsert "~0.0.10" - -"btcp@git+https://github.com/bcoin-org/btcp.git#semver:~0.1.5": - version "0.1.5" - resolved "git+https://github.com/bcoin-org/btcp.git#4ea7e1ce5a43cd5348152c007aff76a419190a3a" - buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -2970,10 +2814,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -"buffer-map@git+https://github.com/chjj/buffer-map.git#semver:~0.0.7": - version "0.0.7" - resolved "git+https://github.com/chjj/buffer-map.git#bad5863af9a520701937a17fc8fa2bd8ca8e73f3" - buffer-reverse@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" @@ -3026,31 +2866,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "^4.3.0" -bufio@^1.0.6, "bufio@git+https://github.com/bcoin-org/bufio.git#semver:~1.0.6", bufio@~1.0.7: +bufio@^1.0.6: version "1.0.7" resolved "git+https://github.com/bcoin-org/bufio.git#91ae6c93899ff9fad7d7cee9afd2a1c4933ca984" -"bupnp@git+https://github.com/bcoin-org/bupnp.git#semver:~0.2.6": - version "0.2.6" - resolved "git+https://github.com/bcoin-org/bupnp.git#c44fa7356aa297c9de96e8ad094a6816939cd688" - dependencies: - binet "~0.3.5" - brq "~0.1.7" - bsert "~0.0.10" - -"bval@git+https://github.com/bcoin-org/bval.git#semver:~0.1.6": - version "0.1.6" - resolved "git+https://github.com/bcoin-org/bval.git#c8cd14419ca46f63610dc48b797b076835e86f48" - dependencies: - bsert "~0.0.10" - -"bweb@git+https://github.com/bcoin-org/bweb.git#semver:=0.1.9": - version "0.1.9" - resolved "git+https://github.com/bcoin-org/bweb.git#31ae94ec9e97079610394e91928fe070d312c39d" - dependencies: - bsert "~0.0.10" - bsock "~0.1.8" - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -5726,10 +5545,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -"loady@git+https://github.com/chjj/loady.git#semver:~0.0.1", loady@~0.0.1, loady@~0.0.5: - version "0.0.5" - resolved "git+https://github.com/chjj/loady.git#b94958b7ee061518f4b85ea6da380e7ee93222d5" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -6148,19 +5963,11 @@ multihashes@^0.4.15, multihashes@~0.4.15: multibase "^0.7.0" varint "^5.0.0" -"n64@git+https://github.com/chjj/n64.git#semver:~0.2.10": - version "0.2.10" - resolved "git+https://github.com/chjj/n64.git#34f981f1441f569821d97a31f8cf21a3fc11b8f6" - nan@^2.13.2, nan@^2.14.0: version "2.15.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -"nan@git+https://github.com/braydonf/nan.git#semver:=2.14.0": - version "2.14.0" - resolved "git+https://github.com/braydonf/nan.git#1dcc61bd06d84e389bfd5311b2b1492a14c74201" - nano-json-stream-parser@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" From b3628409fd5201f564b0c7c36df4f03df1277eb5 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 10 Oct 2023 13:47:00 +0200 Subject: [PATCH 32/43] Update `bitcoinjs-lib` and supported `node` version Here we pull changes from https://github.com/keep-network/tbtc-v2/pull/708 --- typescript/package.json | 4 ++-- typescript/yarn.lock | 47 ++++++++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/typescript/package.json b/typescript/package.json index a518d97fe..27e8a36a4 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -22,7 +22,7 @@ "dependencies": { "@keep-network/ecdsa": "development", "@keep-network/tbtc-v2": "development", - "bitcoinjs-lib": "6.0.2", + "bitcoinjs-lib": "^6.1.5", "bufio": "^1.0.6", "ecpair": "^2.1.0", "electrum-client-js": "git+https://github.com/keep-network/electrum-client-js.git#v0.1.1", @@ -54,6 +54,6 @@ "typescript": "^4.3.5" }, "engines": { - "node": ">=14 <15" + "node": ">=16" } } diff --git a/typescript/yarn.lock b/typescript/yarn.lock index ba01c6262..08928f090 100644 --- a/typescript/yarn.lock +++ b/typescript/yarn.lock @@ -1643,6 +1643,11 @@ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-5.50.0.tgz#29c6419e8379d496ab6d0426eadf3c4d100cd186" integrity sha512-swKHYCOZUGyVt4ge0u8a7AwNcA//h4nx5wIi0sruGye1IJ5Cva0GyK9L2/WdX+kWVTKp92ZiEo1df31lrWGPgA== +"@noble/hashes@^1.2.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2491,6 +2496,11 @@ base-x@^3.0.2, base-x@^3.0.8: dependencies: safe-buffer "^5.0.1" +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2545,7 +2555,7 @@ bindings@^1.3.0: dependencies: file-uri-to-path "1.0.0" -bip174@^2.0.1: +bip174@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.1.tgz#ef3e968cf76de234a546962bcf572cc150982f9f" integrity sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ== @@ -2592,19 +2602,17 @@ bip39@3.0.4: pbkdf2 "^3.0.9" randombytes "^2.0.1" -bitcoinjs-lib@6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.0.2.tgz#0fdf6c41978d93641b936d66f4afce44bb9b7f35" - integrity sha512-I994pGt9cL5s5OA6mkv1e8IuYcsKN2ORXnWbkqAXLNGvEnOHBhKBSvCjFl7YC2uVoJnfr/iwq7JMrq575SYO5w== +bitcoinjs-lib@^6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz#3b03509ae7ddd80a440f10fc38c4a97f0a028d8c" + integrity sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ== dependencies: + "@noble/hashes" "^1.2.0" bech32 "^2.0.0" - bip174 "^2.0.1" - bs58check "^2.1.2" - create-hash "^1.1.0" - ripemd160 "^2.0.2" + bip174 "^2.1.1" + bs58check "^3.0.1" typeforce "^1.11.3" varuint-bitcoin "^1.1.2" - wif "^2.0.1" bl@^1.0.0: version "1.2.3" @@ -2777,6 +2785,13 @@ bs58@^4.0.0: dependencies: base-x "^3.0.2" +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== + dependencies: + base-x "^4.0.0" + bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" @@ -2786,6 +2801,14 @@ bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: create-hash "^1.1.0" safe-buffer "^5.1.2" +bs58check@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-3.0.1.tgz#2094d13720a28593de1cba1d8c4e48602fdd841c" + integrity sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ== + dependencies: + "@noble/hashes" "^1.2.0" + bs58 "^5.0.0" + buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -6742,7 +6765,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: +ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -8629,7 +8652,7 @@ wide-align@1.1.3: dependencies: string-width "^1.0.2 || 2" -wif@2.0.6, wif@^2.0.1, wif@^2.0.6: +wif@2.0.6, wif@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ= From 747c1d15e987d2bb660e1db1adf0aaefb033d95b Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Tue, 10 Oct 2023 13:58:51 +0200 Subject: [PATCH 33/43] Re-format `package.json` file --- typescript/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/package.json b/typescript/package.json index 27e8a36a4..bc3627f9d 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -15,7 +15,9 @@ "build": "npm run typechain && tsc --project tsconfig.build.json", "dev": "tsc --project tsconfig.build.json --watch" }, - "files": ["dist"], + "files": [ + "dist" + ], "config": { "contracts": "./node_modules/@keep-network/ecdsa/artifacts/WalletRegistry.json ./node_modules/@keep-network/tbtc-v2/artifacts/{Bridge,TBTCVault,TBTC}.json" }, From 876af0cfc4a33abca405b82623adbaa3bfa9845f Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Wed, 11 Oct 2023 10:58:38 +0200 Subject: [PATCH 34/43] Rework unit tests structure Here we adjust the unit tests to the recent changes. --- typescript/test/bitcoin-network.test.ts | 67 - typescript/test/deposit-refund.test.ts | 180 -- typescript/test/deposit-sweep.test.ts | 1140 ----------- typescript/test/electrum.test.ts | 290 --- typescript/test/hex.test.ts | 179 -- typescript/test/{ => lib}/bitcoin.test.ts | 456 ++++- typescript/test/lib/electrum.test.ts | 293 +++ typescript/test/{ => lib}/ethereum.test.ts | 6 +- typescript/test/lib/utils.test.ts | 181 ++ typescript/test/proof.test.ts | 394 ---- .../deposits.test.ts} | 187 +- .../maintenance.test.ts} | 1790 ++++++++++------- typescript/test/services/redemptions.test.ts | 839 ++++++++ 13 files changed, 2995 insertions(+), 3007 deletions(-) delete mode 100644 typescript/test/bitcoin-network.test.ts delete mode 100644 typescript/test/deposit-refund.test.ts delete mode 100644 typescript/test/deposit-sweep.test.ts delete mode 100644 typescript/test/electrum.test.ts delete mode 100644 typescript/test/hex.test.ts rename typescript/test/{ => lib}/bitcoin.test.ts (62%) create mode 100644 typescript/test/lib/electrum.test.ts rename typescript/test/{ => lib}/ethereum.test.ts (99%) create mode 100644 typescript/test/lib/utils.test.ts delete mode 100644 typescript/test/proof.test.ts rename typescript/test/{deposit.test.ts => services/deposits.test.ts} (79%) rename typescript/test/{redemption.test.ts => services/maintenance.test.ts} (59%) create mode 100644 typescript/test/services/redemptions.test.ts diff --git a/typescript/test/bitcoin-network.test.ts b/typescript/test/bitcoin-network.test.ts deleted file mode 100644 index b8457f52a..000000000 --- a/typescript/test/bitcoin-network.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { expect } from "chai" -import { BitcoinTxHash, BitcoinNetwork, toBitcoinJsLibNetwork } from "../src" -import { networks } from "bitcoinjs-lib" - -describe("BitcoinNetwork", () => { - const testData = [ - { - enumKey: BitcoinNetwork.Unknown, - enumValue: "unknown", - // any value that doesn't match other supported networks - genesisHash: BitcoinTxHash.from("0x00010203"), - expectedToBitcoinJsLibResult: new Error("network not supported"), - }, - { - enumKey: BitcoinNetwork.Testnet, - enumValue: "testnet", - genesisHash: BitcoinTxHash.from( - "0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" - ), - expectedToBitcoinJsLibResult: networks.testnet, - }, - { - enumKey: BitcoinNetwork.Mainnet, - enumValue: "mainnet", - genesisHash: BitcoinTxHash.from( - "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" - ), - expectedToBitcoinJsLibResult: networks.bitcoin, - }, - ] - - testData.forEach( - ({ enumKey, enumValue, genesisHash, expectedToBitcoinJsLibResult }) => { - context(enumKey, async () => { - describe(`toString`, async () => { - it(`should return correct value`, async () => { - expect(enumKey.toString()).to.be.equal(enumValue) - }) - }) - - describe(`fromGenesisHash`, async () => { - it(`should resolve correct enum key`, async () => { - expect(BitcoinNetwork.fromGenesisHash(genesisHash)).to.be.equal( - enumKey - ) - }) - }) - - describe(`toBitcoinJsLibNetwork`, async () => { - if (expectedToBitcoinJsLibResult instanceof Error) { - it(`should throw an error`, async () => { - expect(() => toBitcoinJsLibNetwork(enumKey)).to.throw( - expectedToBitcoinJsLibResult.message - ) - }) - } else { - it(`should return ${expectedToBitcoinJsLibResult}`, async () => { - expect(toBitcoinJsLibNetwork(enumKey)).to.be.equal( - expectedToBitcoinJsLibResult - ) - }) - } - }) - }) - } - ) -}) diff --git a/typescript/test/deposit-refund.test.ts b/typescript/test/deposit-refund.test.ts deleted file mode 100644 index 722d74566..000000000 --- a/typescript/test/deposit-refund.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { BigNumber } from "ethers" -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import * as chai from "chai" -import chaiAsPromised from "chai-as-promised" -chai.use(chaiAsPromised) -import { expect } from "chai" -import { BitcoinTxHash, BitcoinRawTx } from "./lib/bitcoin" -import { - refunderPrivateKey, - depositRefundOfWitnessDepositAndWitnessRefunderAddress, - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress, - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress, -} from "./data/deposit-refund" -import { DepositRefund } from "../src/services/deposits/refund" -import { DepositScript } from "../src/services/deposits" - -describe("Refund", () => { - const fee = BigNumber.from(1520) - - describe("DepositRefund", () => { - describe("submitTransaction", () => { - let bitcoinClient: MockBitcoinClient - - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - }) - - context("when the refund transaction is requested to be witness", () => { - context("when the refunded deposit was witness", () => { - let transactionHash: BitcoinTxHash - - beforeEach(async () => { - const utxo = - depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit - .utxo - const deposit = - depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit - .data - const refunderAddress = - depositRefundOfWitnessDepositAndWitnessRefunderAddress.refunderAddress - const refunderPrivateKey = - "cTWhf1nXc7aW8BN2qLtWcPtcgcWYKfzRXkCJNsuQ86HR8uJBYfMc" - - const rawTransactions = new Map() - rawTransactions.set(utxo.transactionHash.toString(), { - transactionHex: utxo.transactionHex, - }) - bitcoinClient.rawTransactions = rawTransactions - - const depositRefund = DepositRefund.fromScript( - DepositScript.fromReceipt(deposit) - ) - - ;({ transactionHash } = await depositRefund.submitTransaction( - bitcoinClient, - fee, - utxo, - refunderAddress, - refunderPrivateKey - )) - }) - - it("should broadcast refund transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositRefundOfWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositRefundOfWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transactionHash - ) - }) - }) - - context("when the refunded deposit was non-witness", () => { - let transactionHash: BitcoinTxHash - - beforeEach(async () => { - const utxo = - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.deposit - .utxo - const deposit = - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.deposit - .data - const refunderAddress = - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.refunderAddress - - const rawTransactions = new Map() - rawTransactions.set(utxo.transactionHash.toString(), { - transactionHex: utxo.transactionHex, - }) - bitcoinClient.rawTransactions = rawTransactions - - const depositRefund = DepositRefund.fromScript( - DepositScript.fromReceipt(deposit) - ) - - ;({ transactionHash } = await depositRefund.submitTransaction( - bitcoinClient, - fee, - utxo, - refunderAddress, - refunderPrivateKey - )) - }) - - it("should broadcast refund transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositRefundOfNonWitnessDepositAndWitnessRefunderAddress - .expectedRefund.transactionHash - ) - }) - }) - }) - - context( - "when the refund transaction is requested to be non-witness", - () => { - let transactionHash: BitcoinTxHash - - beforeEach(async () => { - const utxo = - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.deposit - .utxo - const deposit = - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.deposit - .data - const refunderAddress = - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.refunderAddress - - const rawTransactions = new Map() - rawTransactions.set(utxo.transactionHash.toString(), { - transactionHex: utxo.transactionHex, - }) - - const depositRefund = DepositRefund.fromScript( - DepositScript.fromReceipt(deposit) - ) - - bitcoinClient.rawTransactions = rawTransactions - ;({ transactionHash } = await depositRefund.submitTransaction( - bitcoinClient, - fee, - utxo, - refunderAddress, - refunderPrivateKey - )) - }) - - it("should broadcast refund transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress - .expectedRefund.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositRefundOfWitnessDepositAndNonWitnessRefunderAddress - .expectedRefund.transactionHash - ) - }) - } - ) - }) - }) -}) diff --git a/typescript/test/deposit-sweep.test.ts b/typescript/test/deposit-sweep.test.ts deleted file mode 100644 index 54965e208..000000000 --- a/typescript/test/deposit-sweep.test.ts +++ /dev/null @@ -1,1140 +0,0 @@ -import { BigNumber } from "ethers" -import { - BitcoinNetwork, - BitcoinRawTx, - BitcoinTxHash, - BitcoinUtxo, - BitcoinTx, - MaintenanceService, - WalletTx, -} from "../src" -import { testnetWalletAddress, testnetWalletPrivateKey } from "./data/deposit" -import { - depositSweepWithWitnessMainUtxoAndWitnessOutput, - depositSweepWithNoMainUtxoAndWitnessOutput, - depositSweepWithNoMainUtxoAndNonWitnessOutput, - depositSweepWithNonWitnessMainUtxoAndWitnessOutput, - depositSweepProof, - NO_MAIN_UTXO, -} from "./data/deposit-sweep" -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import * as chai from "chai" -import chaiAsPromised from "chai-as-promised" -chai.use(chaiAsPromised) -import { expect } from "chai" -import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" -import { txToJSON } from "./utils/helpers" - -describe("Sweep", () => { - const fee = BigNumber.from(1600) - - describe("WalletTx", () => { - describe("DepositSweep", () => { - describe("submitTransaction", () => { - let tbtcContracts: MockTBTCContracts - let bitcoinClient: MockBitcoinClient - - beforeEach(async () => { - tbtcContracts = new MockTBTCContracts() - bitcoinClient = new MockBitcoinClient() - }) - - context("when the new main UTXO is requested to be witness", () => { - context( - "when there is no main UTXO from previous deposit sweep", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithNoMainUtxoAndWitnessOutput.deposits) { - rawTransactions.set(deposit.utxo.transactionHash.toString(), { - transactionHex: deposit.utxo.transactionHex, - }) - } - bitcoinClient.rawTransactions = rawTransactions - - const utxos: BitcoinUtxo[] = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (data) => { - return data.utxo - } - ) - - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - const witness = - depositSweepWithNoMainUtxoAndWitnessOutput.witness - - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - ;({ transactionHash, newMainUtxo } = - await walletTx.depositSweep.submitTransaction( - fee, - testnetWalletPrivateKey, - utxos, - deposit - )) - }) - - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(35400), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context("when there is main UTXO from previous deposit sweep", () => { - context( - "when main UTXO from previous deposit sweep is witness", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits) { - rawTransactions.set( - deposit.utxo.transactionHash.toString(), - { - transactionHex: deposit.utxo.transactionHex, - } - ) - } - // The main UTXO resulting from another data set was used as input. - // Set raw data of that main UTXO as well. - rawTransactions.set( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString(), - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transaction - ) - bitcoinClient.rawTransactions = rawTransactions - - const utxos: BitcoinUtxo[] = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } - ) - - const deposit = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - - const witness = - depositSweepWithWitnessMainUtxoAndWitnessOutput.witness - - const mainUtxo = - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo - - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - ;({ transactionHash, newMainUtxo } = - await walletTx.depositSweep.submitTransaction( - fee, - testnetWalletPrivateKey, - utxos, - deposit, - mainUtxo - )) - }) - - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithWitnessMainUtxoAndWitnessOutput - .expectedSweep.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash, - outputIndex: 0, - value: BigNumber.from(60800), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context( - "when main UTXO from previous deposit sweep is non-witness", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits) { - rawTransactions.set( - deposit.utxo.transactionHash.toString(), - { - transactionHex: deposit.utxo.transactionHex, - } - ) - } - rawTransactions.set( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString(), - { - transactionHex: - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .mainUtxo.transactionHex, - } - ) - bitcoinClient.rawTransactions = rawTransactions - - const utxos: BitcoinUtxo[] = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } - ) - - const deposit = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - - const witness = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness - - const mainUtxo = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - ;({ transactionHash, newMainUtxo } = - await walletTx.depositSweep.submitTransaction( - fee, - testnetWalletPrivateKey, - utxos, - deposit, - mainUtxo - )) - }) - - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash, - outputIndex: 0, - value: BigNumber.from(33800), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - }) - }) - - context("when the new main UTXO is requested to be non-witness", () => { - // The only difference between deposit sweep transactions with witness and - // non-witness output is the output type itself. - // Therefore only one test case was added for non-witness transactions. - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - - beforeEach(async () => { - // Map transaction hashes for UTXOs to transactions in hexadecimal and - // set the mapping in the mock Bitcoin client - const rawTransactions = new Map() - for (const deposit of depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits) { - rawTransactions.set(deposit.utxo.transactionHash.toString(), { - transactionHex: deposit.utxo.transactionHex, - }) - } - bitcoinClient.rawTransactions = rawTransactions - - const utxos = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (data) => { - return data.utxo - } - ) - - const deposits = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - const witness = - depositSweepWithNoMainUtxoAndNonWitnessOutput.witness - - const walletTx = new WalletTx(tbtcContracts, bitcoinClient, witness) - - ;({ transactionHash, newMainUtxo } = - await walletTx.depositSweep.submitTransaction( - fee, - testnetWalletPrivateKey, - utxos, - deposits - )) - }) - - it("should broadcast sweep transaction with proper structure", async () => { - expect(bitcoinClient.broadcastLog.length).to.be.equal(1) - expect(bitcoinClient.broadcastLog[0]).to.be.eql( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transaction - ) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(13400), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - }) - }) - - describe("assembleTransaction", () => { - let tbtcContracts: MockTBTCContracts - let bitcoinClient: MockBitcoinClient - - beforeEach(async () => { - tbtcContracts = new MockTBTCContracts() - bitcoinClient = new MockBitcoinClient() - }) - - context("when the new main UTXO is requested to be witness", () => { - context( - "when there is no main UTXO from previous deposit sweep", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx - - const utxosWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (data) => { - return data.utxo - } - ) - - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - - const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - utxosWithRaw, - deposit - )) - }) - - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) - - expect(txJSON.hash).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(2) - - const p2shInput = txJSON.inputs[0] - expect(p2shInput.hash).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2shInput.index).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - .outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.empty - expect(p2shInput.script.length).to.be.greaterThan(0) - - const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.hash).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() - ) - expect(p2wshInput.index).to.be.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo - .outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) - const sweepOutput = txJSON.outputs[0] - - // Should be OP_0 . Public key corresponds to the - // wallet BTC address. - expect(sweepOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal(testnetWalletAddress) - // The output's value should be equal to the sum of all input values - // minus fee (25000 + 12000 - 1600) - expect(sweepOutput.value).to.be.equal(35400) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(35400), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context("when there is main UTXO from previous deposit sweep", () => { - context( - "when main UTXO prom previous deposit sweep is witness", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx - - const utxosWithRaw = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } - ) - - const deposit = - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - - // P2WPKH - const mainUtxoWithRaw = - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo - - const witness = - depositSweepWithWitnessMainUtxoAndWitnessOutput.witness - - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - utxosWithRaw, - deposit, - mainUtxoWithRaw - )) - }) - - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithWitnessMainUtxoAndWitnessOutput - .expectedSweep.transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) - - expect(txJSON.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(3) - - const p2wkhInput = txJSON.inputs[0] - expect(p2wkhInput.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() - ) - expect(p2wkhInput.index).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo - .outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wkhInput.witness.length).to.be.greaterThan(0) - expect(p2wkhInput.script.length).to.be.equal(0) - - const p2shInput = txJSON.inputs[1] - expect(p2shInput.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2shInput.index).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0] - .utxo.outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.empty - expect(p2shInput.script.length).to.be.greaterThan(0) - - const p2wshInput = txJSON.inputs[2] - expect(p2wshInput.hash).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() - ) - expect(p2wshInput.index).to.be.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1] - .utxo.outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) - - const sweepOutput = txJSON.outputs[0] - // Should be OP_0 . Public key corresponds to the - // wallet BTC address. - expect(sweepOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal(testnetWalletAddress) - // The output's value should be equal to the sum of all input values - // minus fee (17000 + 10000 + 35400 - 1600) - expect(sweepOutput.value).to.be.equal(60800) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash, - outputIndex: 0, - value: BigNumber.from(60800), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - - context( - "when main UTXO from previous deposit sweep is non-witness", - () => { - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx - - const utxosWithRaw = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } - ) - - const deposit = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - - // P2WPKH - const mainUtxoWithRaw = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - - const witness = - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness - - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - utxosWithRaw, - deposit, - mainUtxoWithRaw - )) - }) - - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) - - expect(txJSON.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(2) - - const p2pkhInput = txJSON.inputs[0] // main UTXO - expect(p2pkhInput.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() - ) - expect(p2pkhInput.index).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo - .outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2pkhInput.witness).to.be.empty - expect(p2pkhInput.script.length).to.be.greaterThan(0) - - const p2wshInput = txJSON.inputs[1] - expect(p2wshInput.hash).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2wshInput.index).to.be.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .deposits[0].utxo.outputIndex - ) - // Transaction should be signed. As it's a SegWit input, the `witness` - // field should be filled, while the `script` field should be empty. - expect(p2wshInput.witness.length).to.be.greaterThan(0) - expect(p2wshInput.script.length).to.be.equal(0) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) - - const sweepOutput = txJSON.outputs[0] - // Should be OP_0 . Public key corresponds to the - // wallet BTC address. - expect(sweepOutput.script).to.be.equal( - "00148db50eb52063ea9d98b3eac91489a90f738986f6" - ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal(testnetWalletAddress) - // The output's value should be equal to the sum of all input values - // minus fee (16400 + 19000 - 1600) - expect(sweepOutput.value).to.be.equal(33800) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNonWitnessMainUtxoAndWitnessOutput - .expectedSweep.transactionHash, - outputIndex: 0, - value: BigNumber.from(33800), - } - - expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) - }) - } - ) - }) - }) - - context("when the new main UTXO is requested to be non-witness", () => { - // The only difference between deposit sweep transactions with witness and - // non-witness output is the output type itself. - // Therefore only one test case was added for non-witness transactions. - let transactionHash: BitcoinTxHash - let newMainUtxo: BitcoinUtxo - let transaction: BitcoinRawTx - - const utxosWithRaw = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (deposit) => { - return deposit.utxo - } - ) - - const deposit = - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( - (deposit) => { - return deposit.data - } - ) - - const witness = depositSweepWithNoMainUtxoAndNonWitnessOutput.witness - - const walletTx = new WalletTx(tbtcContracts, bitcoinClient, witness) - - beforeEach(async () => { - ;({ - transactionHash, - newMainUtxo, - rawTransaction: transaction, - } = await walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - utxosWithRaw, - deposit - )) - }) - - it("should return sweep transaction with proper structure", () => { - // Compare HEXes. - expect(transaction).to.be.eql( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transaction - ) - - // Convert raw transaction to JSON to make detailed comparison. - const txJSON = txToJSON( - transaction.transactionHex, - BitcoinNetwork.Testnet - ) - - expect(txJSON.hash).to.be.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep.transactionHash.toString() - ) - expect(txJSON.version).to.be.equal(1) - - // Validate inputs. - expect(txJSON.inputs.length).to.be.equal(1) - - const p2shInput = txJSON.inputs[0] - expect(p2shInput.hash).to.be.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo.transactionHash.toString() - ) - expect(p2shInput.index).to.be.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo - .outputIndex - ) - // Transaction should be signed. As it's not SegWit input, the `witness` - // field should be empty, while the `script` field should be filled. - expect(p2shInput.witness).to.be.empty - expect(p2shInput.script.length).to.be.greaterThan(0) - - // Validate outputs. - expect(txJSON.outputs.length).to.be.equal(1) - - const sweepOutput = txJSON.outputs[0] - // OP_DUP OP_HASH160 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 - // OP_EQUALVERIFY OP_CHECKSIG - expect(sweepOutput.script).to.be.equal( - "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac" - ) - // The output's address should be the wallet's address - expect(sweepOutput.address).to.be.equal( - "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" - ) - // The output's value should be equal to the sum of all input values - // minus fee (15000- 1600) - expect(sweepOutput.value).to.be.equal(13400) - }) - - it("should return the proper transaction hash", async () => { - expect(transactionHash).to.be.deep.equal( - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash - ) - }) - - it("should return the proper new main UTXO", () => { - const expectedNewMainUtxo = { - transactionHash: - depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep - .transactionHash, - outputIndex: 0, - value: BigNumber.from(13400), - } - - expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) - }) - }) - - context("when there are no UTXOs", () => { - it("should revert", async () => { - const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - - await expect( - walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - [], - [] - ) - ).to.be.rejectedWith( - "There must be at least one deposit UTXO to sweep" - ) - }) - }) - - context( - "when the numbers of UTXOs and deposit elements are not equal", - () => { - const utxosWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( - (data) => { - return data.utxo - } - ) - - // Add only one element to the deposit - const deposit = [ - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data, - ] - - const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - - it("should revert", async () => { - const walletTx = new WalletTx( - tbtcContracts, - bitcoinClient, - witness - ) - - await expect( - walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - utxosWithRaw, - deposit - ) - ).to.be.rejectedWith( - "Number of UTXOs must equal the number of deposit elements" - ) - }) - } - ) - - context("when the main UTXO does not belong to the wallet", () => { - const utxoWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - - // The UTXO below does not belong to the wallet - const mainUtxoWithRaw = { - transactionHash: BitcoinTxHash.from( - "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883" - ), - outputIndex: 1, - value: BigNumber.from(3933200), - transactionHex: - "0100000000010162cae24e74ad64f9f0493b09f3964908b3b3038f4924882d3d" + - "bd853b4c9bc7390100000000ffffffff02102700000000000017a914867120d5" + - "480a9cc0c11c1193fa59b3a92e852da78710043c00000000001600147ac2d937" + - "8a1c47e589dfb8095ca95ed2140d272602483045022100b70bd9b7f5d230444a" + - "542c7971bea79786b4ebde6703cee7b6ee8cd16e115ebf02204d50ea9d1ee08d" + - "e9741498c2cc64266e40d52c4adb9ef68e65aa2727cd4208b5012102ee067a02" + - "73f2e3ba88d23140a24fdb290f27bbcd0f94117a9c65be3911c5c04e00000000", - } - - it("should revert", async () => { - const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - - await expect( - walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - [utxoWithRaw], - [deposit], - mainUtxoWithRaw - ) - ).to.be.rejectedWith("UTXO does not belong to the wallet") - }) - }) - - context( - "when the wallet private does not correspond to the wallet public key", - () => { - const utxoWithRaw = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - const anotherPrivateKey = - "cRJvyxtoggjAm9A94cB86hZ7Y62z2ei5VNJHLksFi2xdnz1GJ6xt" - - it("should revert", async () => { - const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - - await expect( - walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - anotherPrivateKey, - [utxoWithRaw], - [deposit] - ) - ).to.be.rejectedWith( - "Wallet public key does not correspond to wallet private key" - ) - }) - } - ) - - context("when the type of UTXO is unsupported", () => { - // Use coinbase transaction of some block - const utxoWithRaw = { - transactionHash: BitcoinTxHash.from( - "025de155e6f2ffbbf4851493e0d28dad54020db221a3f38bf63c1f65e3d3595b" - ), - outputIndex: 0, - value: BigNumber.from(5000000000), - transactionHex: - "010000000100000000000000000000000000000000000000000000000000000000" + - "00000000ffffffff0e04db07c34f0103062f503253482fffffffff0100f2052a01" + - "000000232102db6a0f2ef2e970eb1d2a84eabb5337f9cac0d85b49f209bffc4ec6" + - "805802e6a5ac00000000", - } - const deposit = - depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - - it("should revert", async () => { - const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - - await expect( - walletTx.depositSweep.assembleTransaction( - BitcoinNetwork.Testnet, - fee, - testnetWalletPrivateKey, - [utxoWithRaw], - [deposit] - ) - ).to.be.rejectedWith("Unsupported UTXO script type") - }) - }) - }) - }) - }) - - describe("Spv", () => { - describe("submitDepositSweepProof", () => { - let bitcoinClient: MockBitcoinClient - let tbtcContracts: MockTBTCContracts - let maintenanceService: MaintenanceService - - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - tbtcContracts = new MockTBTCContracts() - - maintenanceService = new MaintenanceService( - tbtcContracts, - bitcoinClient - ) - - const transactionHash = - depositSweepProof.bitcoinChainData.transaction.transactionHash - const transactions = new Map() - transactions.set( - transactionHash.toString(), - depositSweepProof.bitcoinChainData.transaction - ) - bitcoinClient.transactions = transactions - - const rawTransactions = new Map() - rawTransactions.set( - transactionHash.toString(), - depositSweepProof.bitcoinChainData.rawTransaction - ) - bitcoinClient.rawTransactions = rawTransactions - - bitcoinClient.latestHeight = - depositSweepProof.bitcoinChainData.latestBlockHeight - bitcoinClient.headersChain = - depositSweepProof.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = - depositSweepProof.bitcoinChainData.transactionMerkleBranch - const confirmations = new Map() - confirmations.set( - transactionHash.toString(), - depositSweepProof.bitcoinChainData.accumulatedTxConfirmations - ) - bitcoinClient.confirmations = confirmations - await maintenanceService.spv.submitDepositSweepProof( - transactionHash, - NO_MAIN_UTXO - ) - }) - - it("should submit deposit sweep proof with correct arguments", () => { - const bridgeLog = tbtcContracts.bridge.depositSweepProofLog - expect(bridgeLog.length).to.equal(1) - expect(bridgeLog[0].mainUtxo).to.equal(NO_MAIN_UTXO) - expect(bridgeLog[0].sweepTx).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepTx - ) - expect(bridgeLog[0].sweepProof.txIndexInBlock).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepProof.txIndexInBlock - ) - expect(bridgeLog[0].sweepProof.merkleProof).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepProof.merkleProof - ) - expect(bridgeLog[0].sweepProof.bitcoinHeaders).to.deep.equal( - depositSweepProof.expectedSweepProof.sweepProof.bitcoinHeaders - ) - }) - }) - }) -}) diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts deleted file mode 100644 index 6d8ff8730..000000000 --- a/typescript/test/electrum.test.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { - BitcoinNetwork, - ElectrumCredentials, - ElectrumClient, - computeElectrumScriptHash, -} from "../src" -import { - testnetAddress, - testnetHeadersChain, - testnetRawTransaction, - testnetTransaction, - testnetTransactionMerkleBranch, - testnetUTXO, -} from "./data/electrum" -import { expect } from "chai" -import https from "https" - -const BLOCKSTREAM_TESTNET_API_URL = "https://blockstream.info/testnet/api" - -const testnetCredentials: ElectrumCredentials[] = [ - // FIXME: Enable all protocols test for test.tbtc.network servers once they are - // publicly exposed. - // // electrumx tcp - // { - // host: "electrumx-server.test.tbtc.network", - // port: 80, - // protocol: "tcp", - // }, - // electrumx ssl - // { - // host: "electrumx-server.test.tbtc.network", - // port: 443, - // protocol: "ssl", - // }, - // electrumx ws - // { - // host: "electrumx-server.test.tbtc.network", - // port: 8080, - // protocol: "ws", - // }, - // electrumx wss - { - host: "electrumx-server.test.tbtc.network", - port: 8443, - protocol: "wss", - }, - // electrs-esplora tcp - { - host: "electrum.blockstream.info", - port: 60001, - protocol: "tcp", - }, - // FIXME: https://github.com/keep-network/tbtc-v2/issues/502 - // // electrs-esplora ssl - // { - // host: "electrum.blockstream.info", - // port: 60002, - // protocol: "ssl", - // }, - // fulcrum tcp - { - host: "testnet.aranguren.org", - port: 51001, - protocol: "tcp", - }, - // FIXME: https://github.com/keep-network/tbtc-v2/issues/502 - // fulcrum ssl - // { - // host: "testnet.aranguren.org", - // port: 51002, - // protocol: "ssl", - // }, -] - -/** - * This test suite is meant to check the behavior of the Electrum-based - * Bitcoin client implementation. This suite requires an integration with a - * real testnet Electrum server. That requirement makes those tests - * time-consuming and vulnerable to external service health fluctuations. - * Because of that, they are skipped by default and should be run only - * on demand. Worth noting this test suite does not provide full coverage - * of all Electrum client functions. The `broadcast` function is not covered - * since it requires a proper Bitcoin transaction hex for each run which is - * out of scope of this suite. The `broadcast` function was tested manually - * though. - */ -describe.skip("Electrum", () => { - testnetCredentials.forEach((credentials) => { - describe(`${credentials.protocol}://${credentials.host}:${credentials.port}`, async () => { - let electrumClient: ElectrumClient - - before(async () => { - electrumClient = new ElectrumClient([credentials]) - }) - - describe("getNetwork", () => { - it("should return proper network", async () => { - const result = await electrumClient.getNetwork() - expect(result).to.be.eql(BitcoinNetwork.Testnet) - }) - }) - - describe("findAllUnspentTransactionOutputs", () => { - it("should return proper UTXOs for the given address", async () => { - const result = await electrumClient.findAllUnspentTransactionOutputs( - testnetAddress - ) - expect(result).to.be.eql([testnetUTXO]) - }) - }) - - describe("getTransactionHistory", () => { - it("should return proper transaction history for the given address", async () => { - // https://live.blockcypher.com/btc-testnet/address/tb1qumuaw3exkxdhtut0u85latkqfz4ylgwstkdzsx - const transactions = await electrumClient.getTransactionHistory( - "tb1qumuaw3exkxdhtut0u85latkqfz4ylgwstkdzsx", - 5 - ) - - const transactionsHashes = transactions.map((t) => - t.transactionHash.toString() - ) - - expect(transactionsHashes).to.be.eql([ - "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", - "f65bc5029251f0042aedb37f90dbb2bfb63a2e81694beef9cae5ec62e954c22e", - "44863a79ce2b8fec9792403d5048506e50ffa7338191db0e6c30d3d3358ea2f6", - "4c6b33b7c0550e0e536a5d119ac7189d71e1296fcb0c258e0c115356895bc0e6", - "605edd75ae0b4fa7cfc7aae8f1399119e9d7ecc212e6253156b60d60f4925d44", - ]) - }) - }) - - describe("getTransaction", () => { - it("should return proper transaction for the given hash", async () => { - const result = await electrumClient.getTransaction( - testnetTransaction.transactionHash - ) - expect(result).to.be.eql(testnetTransaction) - }) - - // TODO: Add case when transaction doesn't exist. - }) - - describe("getRawTransaction", () => { - it("should return proper raw transaction for the given hash", async () => { - const result = await electrumClient.getRawTransaction( - testnetTransaction.transactionHash - ) - expect(result).to.be.eql(testnetRawTransaction) - }) - }) - - describe("getTransactionConfirmations", () => { - let result: number - - before(async () => { - result = await electrumClient.getTransactionConfirmations( - testnetTransaction.transactionHash - ) - }) - - it("should return value greater than 6", async () => { - // Strict comparison is not possible as the number of confirmations - // constantly grows. We just make sure it's 6+. - expect(result).to.be.greaterThan(6) - }) - - // This test depends on `latestBlockHeight` function. - it("should return proper confirmations number for the given hash", async () => { - const latestBlockHeight = await electrumClient.latestBlockHeight() - - const expectedResult = - latestBlockHeight - testnetTransactionMerkleBranch.blockHeight - - expect(result).to.be.closeTo(expectedResult, 3) - }) - }) - - describe("latestBlockHeight", () => { - let result: number - - before(async () => { - result = await electrumClient.latestBlockHeight() - }) - - it("should return value greater than 6", async () => { - // Strict comparison is not possible as the latest block height - // constantly grows. We just make sure it's bigger than 0. - expect(result).to.be.greaterThan(0) - }) - - // This test depends on fetching the expected latest block height from Blockstream API. - // It can fail if Blockstream API is down or if Blockstream API or if - // Electrum Server used in tests is out-of-sync with the Blockstream API. - it("should return proper latest block height", async () => { - const expectedResult = await getExpectedLatestBlockHeight() - - expect(result).to.be.closeTo(expectedResult, 3) - }) - }) - - describe("getHeadersChain", () => { - it("should return proper headers chain", async () => { - const result = await electrumClient.getHeadersChain( - testnetHeadersChain.blockHeight, - testnetHeadersChain.headersChainLength - ) - expect(result).to.be.eql(testnetHeadersChain.headersChain) - }) - }) - - describe("getTransactionMerkle", () => { - it("should return proper transaction merkle", async () => { - const result = await electrumClient.getTransactionMerkle( - testnetTransaction.transactionHash, - testnetTransactionMerkleBranch.blockHeight - ) - expect(result).to.be.eql(testnetTransactionMerkleBranch) - }) - }) - - describe("computeElectrumScriptHash", () => { - it("should convert Bitcoin script to an Electrum script hash correctly", () => { - const script = "00144b47c798d12edd17dfb4ea98e5447926f664731c" - const expectedScriptHash = - "cabdea0bfc10fb3521721dde503487dd1f0e41dd6609da228066757563f292ab" - - expect(computeElectrumScriptHash(script)).to.be.equal( - expectedScriptHash - ) - }) - }) - }) - }) - - describe("fallback connection", async () => { - let electrumClient: ElectrumClient - - before(async () => { - electrumClient = new ElectrumClient( - [ - // Invalid server - { - host: "non-existing-host.local", - port: 50001, - protocol: "tcp", - }, - // Valid server - testnetCredentials[0], - ], - undefined, - 2, - 1000, - 2000 - ) - }) - - it("should establish connection with a fallback server", async () => { - const result = await electrumClient.getNetwork() - expect(result).to.be.eql(BitcoinNetwork.Testnet) - }) - }) -}) - -/** - * Gets the height of the last block fetched from the Blockstream API. - * @returns Height of the last block. - */ -function getExpectedLatestBlockHeight(): Promise { - return new Promise((resolve, reject) => { - https - .get(`${BLOCKSTREAM_TESTNET_API_URL}/blocks/tip/height`, (resp) => { - let data = "" - - // A chunk of data has been received. - resp.on("data", (chunk) => { - data += chunk - }) - - // The whole response has been received. Print out the result. - resp.on("end", () => { - resolve(JSON.parse(data)) - }) - }) - .on("error", (err) => { - reject(err) - }) - }) -} diff --git a/typescript/test/hex.test.ts b/typescript/test/hex.test.ts deleted file mode 100644 index 252f1a93d..000000000 --- a/typescript/test/hex.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { assert } from "chai" -import { Hex } from "../src" - -describe("Hex", () => { - const stringUnprefixed = - "6e30e6da7a7881a70de2a1cf67b77f2f643e58eb0d055b0f5ef85802a9167727" - const stringPrefixed = - "0x6e30e6da7a7881a70de2a1cf67b77f2f643e58eb0d055b0f5ef85802a9167727" - const stringReversed = - "277716a90258f85e0f5b050deb583e642f7fb767cfa1e20da781787adae6306e" - - const validInputTests = [ - { - name: "unprefixed string", - input: stringUnprefixed, - expectedString: stringUnprefixed, - expectedPrefixedString: stringPrefixed, - }, - { - name: "prefixed string", - input: stringPrefixed, - expectedString: stringUnprefixed, - expectedPrefixedString: stringPrefixed, - }, - { - name: "prefixed uppercase string", - input: stringPrefixed.toUpperCase(), - expectedString: stringUnprefixed, - expectedPrefixedString: stringPrefixed, - }, - { - name: "string with leading and trailing zeros", - input: "00000a12d073c00000", - expectedString: "00000a12d073c00000", - expectedPrefixedString: "0x00000a12d073c00000", - }, - { - name: "empty string", - input: "", - expectedString: "", - expectedPrefixedString: "", - }, - { - name: "unprefixed buffer", - input: Buffer.from(stringUnprefixed, "hex"), - expectedString: stringUnprefixed, - expectedPrefixedString: stringPrefixed, - }, - { - name: "unprefixed uppercase buffer", - input: Buffer.from(stringUnprefixed.toUpperCase(), "hex"), - expectedString: stringUnprefixed, - expectedPrefixedString: stringPrefixed, - }, - { - name: "empty buffer", - input: Buffer.from("", "hex"), - expectedString: "", - expectedPrefixedString: "", - }, - ] - - const invalidInputTests = [ - { - name: "string with a character out of 0-9,a-z,A-Z", - input: "3a9f5G", - expectedErrorMessage: "invalid format of hex string", - }, - { - name: "string of odd length", - input: "ab12345", - expectedErrorMessage: "invalid length of hex string: 7", - }, - ] - - validInputTests.forEach( - ({ name, input, expectedString, expectedPrefixedString }) => { - context(`with input as ${name}`, () => { - const hex = Hex.from(input) - - describe("`${hex}`", () => { - it("should output expected string", () => { - const actual = `${hex}` - assert.equal(actual, expectedString) - }) - }) - - describe("toString", () => { - it("should output expected string", () => { - const actual = hex.toString() - assert.equal(actual, expectedString) - }) - }) - - describe("toPrefixedString", () => { - it("should output expected string", () => { - const actual = hex.toPrefixedString() - assert.equal(actual, expectedPrefixedString) - }) - }) - }) - } - ) - - invalidInputTests.forEach(({ name, input, expectedErrorMessage }) => { - context(`with input as ${name}`, () => { - it(`should throw error with message: ${expectedErrorMessage}`, () => { - assert.throws( - () => { - Hex.from(input) - }, - Error, - expectedErrorMessage - ) - }) - }) - }) - - describe("reverse", () => { - const hex = Hex.from(stringPrefixed) - const hexReversed = hex.reverse() - - it("should not modify source hex", () => { - assert.equal(hex.toString(), stringUnprefixed) - }) - - it("should reverse target hex", () => { - assert.equal(hexReversed.toString(), stringReversed) - }) - }) - - describe("toBuffer", () => { - const hex = Hex.from(stringPrefixed) - const expectedBuffer = Buffer.from(stringUnprefixed, "hex") - - it("should output a buffer", () => { - assert.deepEqual(hex.toBuffer(), expectedBuffer) - }) - - it("should not modify source hex when target buffer is changed", () => { - const buffer = hex.toBuffer() - buffer.reverse() - - assert.equal(hex.toString(), stringUnprefixed) - }) - }) - - describe("equals", () => { - const hexLowerCased = Hex.from(stringPrefixed.toLowerCase()) - const hexUpperCased = Hex.from(stringPrefixed.toUpperCase()) - - context("for the same values with matching cases", () => { - it("should return true", () => { - assert.isTrue(hexUpperCased.equals(hexUpperCased)) - }) - }) - - context("for the same values but not matching cases", () => { - it("should return true", () => { - assert.isTrue(hexLowerCased.equals(hexUpperCased)) - }) - }) - - context("for the same value but prefixed and unprefixed", () => { - it("should return true", () => { - assert.isTrue( - Hex.from(stringPrefixed).equals(Hex.from(stringUnprefixed)) - ) - }) - }) - - context("for different values", () => { - it("should return false", () => { - const otherValue: Hex = Hex.from(stringPrefixed.slice(-2)) - assert.isFalse(hexLowerCased.equals(otherValue)) - }) - }) - }) -}) diff --git a/typescript/test/bitcoin.test.ts b/typescript/test/lib/bitcoin.test.ts similarity index 62% rename from typescript/test/bitcoin.test.ts rename to typescript/test/lib/bitcoin.test.ts index 6b5b2f206..8483732ba 100644 --- a/typescript/test/bitcoin.test.ts +++ b/typescript/test/lib/bitcoin.test.ts @@ -7,17 +7,98 @@ import { BitcoinHeaderSerializer, BitcoinLocktimeUtils, BitcoinNetwork, + toBitcoinJsLibNetwork, BitcoinPublicKeyUtils, BitcoinScriptUtils, BitcoinTargetConverter, extractBitcoinRawTxVectors, Hex, -} from "../src" + BitcoinTxHash, + BitcoinTx, + BitcoinSpvProof, + assembleBitcoinSpvProof, + validateBitcoinSpvProof, +} from "../../src" import { BigNumber } from "ethers" -import { btcAddresses, btcAddressFromPublicKey } from "./data/bitcoin" -import { depositSweepWithNoMainUtxoAndWitnessOutput } from "./data/deposit-sweep" +import { btcAddresses, btcAddressFromPublicKey } from "../data/bitcoin" +import { depositSweepWithNoMainUtxoAndWitnessOutput } from "../data/deposit-sweep" +import { networks } from "bitcoinjs-lib" +import { MockBitcoinClient } from "../utils/mock-bitcoin-client" +import { + multipleInputsProofTestData, + ProofTestData, + singleInputProofTestData, + testnetTransactionData, + transactionConfirmationsInOneEpochData, + transactionConfirmationsInTwoEpochsData, + TransactionProofData, +} from "../data/proof" describe("Bitcoin", () => { + describe("BitcoinNetwork", () => { + const testData = [ + { + enumKey: BitcoinNetwork.Unknown, + enumValue: "unknown", + // any value that doesn't match other supported networks + genesisHash: BitcoinTxHash.from("0x00010203"), + expectedToBitcoinJsLibResult: new Error("network not supported"), + }, + { + enumKey: BitcoinNetwork.Testnet, + enumValue: "testnet", + genesisHash: BitcoinTxHash.from( + "0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + ), + expectedToBitcoinJsLibResult: networks.testnet, + }, + { + enumKey: BitcoinNetwork.Mainnet, + enumValue: "mainnet", + genesisHash: BitcoinTxHash.from( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ), + expectedToBitcoinJsLibResult: networks.bitcoin, + }, + ] + + testData.forEach( + ({ enumKey, enumValue, genesisHash, expectedToBitcoinJsLibResult }) => { + context(enumKey, async () => { + describe(`toString`, async () => { + it(`should return correct value`, async () => { + expect(enumKey.toString()).to.be.equal(enumValue) + }) + }) + + describe(`fromGenesisHash`, async () => { + it(`should resolve correct enum key`, async () => { + expect(BitcoinNetwork.fromGenesisHash(genesisHash)).to.be.equal( + enumKey + ) + }) + }) + + describe(`toBitcoinJsLibNetwork`, async () => { + if (expectedToBitcoinJsLibResult instanceof Error) { + it(`should throw an error`, async () => { + expect(() => toBitcoinJsLibNetwork(enumKey)).to.throw( + expectedToBitcoinJsLibResult.message + ) + }) + } else { + it(`should return ${expectedToBitcoinJsLibResult}`, async () => { + expect(toBitcoinJsLibNetwork(enumKey)).to.be.equal( + expectedToBitcoinJsLibResult + ) + }) + } + }) + }) + } + ) + }) + describe("BitcoinPublicKeyUtils", () => { const { compressPublicKey } = BitcoinPublicKeyUtils @@ -803,4 +884,373 @@ describe("Bitcoin", () => { expect(decomposedTransaction.locktime).to.be.equal("00000000") }) }) + + describe("assembleTransactionProof", () => { + let bitcoinClient: MockBitcoinClient + + beforeEach(async () => { + bitcoinClient = new MockBitcoinClient() + }) + + context("when the transaction has one input", () => { + let proof: BitcoinTx & BitcoinSpvProof + + beforeEach(async () => { + proof = await runProofScenario(singleInputProofTestData) + }) + + it("should return the correct value of the proof", async () => { + const expectedProof = singleInputProofTestData.expectedProof + expect(proof.transactionHash).to.be.deep.equal( + expectedProof.transactionHash + ) + expect(proof.inputs).to.deep.equal(expectedProof.inputs) + expect(proof.outputs).to.deep.equal(expectedProof.outputs) + expect(proof.merkleProof).to.equal(expectedProof.merkleProof) + expect(proof.txIndexInBlock).to.equal(expectedProof.txIndexInBlock) + expect(proof.bitcoinHeaders).to.equal(expectedProof.bitcoinHeaders) + }) + }) + + context("when the transaction has multiple inputs", () => { + let proof: BitcoinTx & BitcoinSpvProof + + beforeEach(async () => { + proof = await runProofScenario(multipleInputsProofTestData) + }) + + it("should return the correct value of the proof", async () => { + const expectedProof = multipleInputsProofTestData.expectedProof + expect(proof.transactionHash).to.deep.equal( + expectedProof.transactionHash + ) + expect(proof.inputs).to.deep.equal(expectedProof.inputs) + expect(proof.outputs).to.deep.equal(expectedProof.outputs) + expect(proof.merkleProof).to.equal(expectedProof.merkleProof) + expect(proof.txIndexInBlock).to.equal(expectedProof.txIndexInBlock) + expect(proof.bitcoinHeaders).to.equal(expectedProof.bitcoinHeaders) + }) + }) + + context("when the transaction does not have enough confirmations", () => { + let notEnoughConfirmationsSweepProofTestData: ProofTestData + + beforeEach(async () => { + notEnoughConfirmationsSweepProofTestData = singleInputProofTestData + notEnoughConfirmationsSweepProofTestData.bitcoinChainData.accumulatedTxConfirmations = 5 + }) + + it("should revert", async () => { + await expect( + runProofScenario(notEnoughConfirmationsSweepProofTestData) + ).to.be.rejectedWith( + "Transaction confirmations number[5] is not enough, required [6]" + ) + }) + }) + + async function runProofScenario( + data: ProofTestData + ): Promise { + const transactions = new Map() + const transactionHash = data.bitcoinChainData.transaction.transactionHash + transactions.set( + transactionHash.toString(), + data.bitcoinChainData.transaction + ) + bitcoinClient.transactions = transactions + bitcoinClient.latestHeight = data.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = data.bitcoinChainData.headersChain + bitcoinClient.transactionMerkle = + data.bitcoinChainData.transactionMerkleBranch + const confirmations = new Map() + confirmations.set( + transactionHash.toString(), + data.bitcoinChainData.accumulatedTxConfirmations + ) + bitcoinClient.confirmations = confirmations + + const proof = await assembleBitcoinSpvProof( + transactionHash, + data.requiredConfirmations, + bitcoinClient + ) + + return proof + } + }) + + describe("validateTransactionProof", () => { + let bitcoinClient: MockBitcoinClient + + beforeEach(async () => { + bitcoinClient = new MockBitcoinClient() + }) + + context("when the transaction proof is correct", () => { + context("when the transaction is from Bitcoin Mainnet", () => { + context( + "when the transaction confirmations span only one epoch", + () => { + it("should not throw", async () => { + await expect( + runProofValidationScenario( + transactionConfirmationsInOneEpochData + ) + ).not.to.be.rejected + }) + } + ) + + context("when the transaction confirmations span two epochs", () => { + it("should not throw", async () => { + await expect( + runProofValidationScenario( + transactionConfirmationsInTwoEpochsData + ) + ).not.to.be.rejected + }) + }) + }) + + context("when the transaction is from Bitcoin Testnet", () => { + it("should not throw", async () => { + await expect(runProofValidationScenario(testnetTransactionData)).not + .to.be.rejected + }) + }) + }) + + context("when the transaction proof is incorrect", () => { + context("when the length of headers chain is incorrect", () => { + it("should throw", async () => { + // Corrupt data by adding additional byte to the headers chain. + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + headersChain: + transactionConfirmationsInOneEpochData.bitcoinChainData + .headersChain + "ff", + }, + } + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith("Incorrect length of Bitcoin headers") + }) + }) + + context( + "when the headers chain contains an incorrect number of headers", + () => { + // Corrupt the data by adding additional 80 bytes to the headers chain. + it("should throw", async () => { + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + headersChain: + transactionConfirmationsInOneEpochData.bitcoinChainData + .headersChain + "f".repeat(160), + }, + } + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith("Wrong number of confirmations") + }) + } + ) + + context("when the merkle proof is of incorrect length", () => { + it("should throw", async () => { + // Corrupt the data by adding a byte to the Merkle proof. + const merkle = [ + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .transactionMerkleBranch.merkle, + ] + merkle[merkle.length - 1] += "ff" + + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + transactionMerkleBranch: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .transactionMerkleBranch, + merkle: merkle, + }, + }, + } + + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith("Incorrect length of Merkle proof") + }) + }) + + context("when the merkle proof is empty", () => { + it("should throw", async () => { + // Corrupt the data by making the Merkle proof empty. + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + transactionMerkleBranch: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .transactionMerkleBranch, + merkle: [], + }, + }, + } + + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith("Invalid merkle tree") + }) + }) + + context("when the merkle proof contains incorrect hash", () => { + it("should throw", async () => { + // Corrupt the data by changing a byte of one of the hashes in the + // Merkle proof. + const merkle = [ + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .transactionMerkleBranch.merkle, + ] + + merkle[3] = "ff" + merkle[3].slice(2) + + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + transactionMerkleBranch: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData + .transactionMerkleBranch, + merkle: merkle, + }, + }, + } + + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith( + "Transaction Merkle proof is not valid for provided header and transaction hash" + ) + }) + }) + + context("when the block headers do not form a chain", () => { + it("should throw", async () => { + // Corrupt data by modifying previous block header hash of one of the + // headers. + const headers: BitcoinHeader[] = + BitcoinHeaderSerializer.deserializeHeadersChain( + transactionConfirmationsInOneEpochData.bitcoinChainData + .headersChain + ) + headers[headers.length - 1].previousBlockHeaderHash = Hex.from( + "ff".repeat(32) + ) + const corruptedHeadersChain: string = headers + .map(BitcoinHeaderSerializer.serializeHeader) + .join("") + + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + headersChain: corruptedHeadersChain, + }, + } + + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith("Invalid headers chain") + }) + }) + + context("when one of the block headers has insufficient work", () => { + it("should throw", async () => { + // Corrupt data by modifying the nonce of one of the headers, so that + // the resulting hash will be above the required difficulty target. + const headers: BitcoinHeader[] = + BitcoinHeaderSerializer.deserializeHeadersChain( + transactionConfirmationsInOneEpochData.bitcoinChainData + .headersChain + ) + headers[headers.length - 1].nonce++ + const corruptedHeadersChain: string = headers + .map(BitcoinHeaderSerializer.serializeHeader) + .join("") + + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInOneEpochData, + bitcoinChainData: { + ...transactionConfirmationsInOneEpochData.bitcoinChainData, + headersChain: corruptedHeadersChain, + }, + } + + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith("Insufficient work in the header") + }) + }) + + context( + "when some of the block headers are not at current or previous difficulty", + () => { + it("should throw", async () => { + // Corrupt data by setting current difficulty to a different value + // than stored in block headers. + const corruptedProofData: TransactionProofData = { + ...transactionConfirmationsInTwoEpochsData, + bitcoinChainData: { + ...transactionConfirmationsInTwoEpochsData.bitcoinChainData, + currentDifficulty: + transactionConfirmationsInTwoEpochsData.bitcoinChainData.currentDifficulty.add( + 1 + ), + }, + } + + await expect( + runProofValidationScenario(corruptedProofData) + ).to.be.rejectedWith( + "Header difficulty not at current or previous Bitcoin difficulty" + ) + }) + } + ) + }) + + async function runProofValidationScenario(data: TransactionProofData) { + const transactions = new Map() + const transactionHash = data.bitcoinChainData.transaction.transactionHash + transactions.set( + transactionHash.toString(), + data.bitcoinChainData.transaction + ) + bitcoinClient.transactions = transactions + bitcoinClient.latestHeight = data.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = data.bitcoinChainData.headersChain + bitcoinClient.transactionMerkle = + data.bitcoinChainData.transactionMerkleBranch + const confirmations = new Map() + confirmations.set( + transactionHash.toString(), + data.bitcoinChainData.accumulatedTxConfirmations + ) + bitcoinClient.confirmations = confirmations + + await validateBitcoinSpvProof( + data.bitcoinChainData.transaction.transactionHash, + data.requiredConfirmations, + data.bitcoinChainData.previousDifficulty, + data.bitcoinChainData.currentDifficulty, + bitcoinClient + ) + } + }) }) diff --git a/typescript/test/lib/electrum.test.ts b/typescript/test/lib/electrum.test.ts new file mode 100644 index 000000000..76f521fc0 --- /dev/null +++ b/typescript/test/lib/electrum.test.ts @@ -0,0 +1,293 @@ +import { + BitcoinNetwork, + ElectrumCredentials, + ElectrumClient, + computeElectrumScriptHash, +} from "../../src" +import { + testnetAddress, + testnetHeadersChain, + testnetRawTransaction, + testnetTransaction, + testnetTransactionMerkleBranch, + testnetUTXO, +} from "../data/electrum" +import { expect } from "chai" +import https from "https" + +const BLOCKSTREAM_TESTNET_API_URL = "https://blockstream.info/testnet/api" + +const testnetCredentials: ElectrumCredentials[] = [ + // FIXME: Enable all protocols test for test.tbtc.network servers once they are + // publicly exposed. + // // electrumx tcp + // { + // host: "electrumx-server.test.tbtc.network", + // port: 80, + // protocol: "tcp", + // }, + // electrumx ssl + // { + // host: "electrumx-server.test.tbtc.network", + // port: 443, + // protocol: "ssl", + // }, + // electrumx ws + // { + // host: "electrumx-server.test.tbtc.network", + // port: 8080, + // protocol: "ws", + // }, + // electrumx wss + { + host: "electrumx-server.test.tbtc.network", + port: 8443, + protocol: "wss", + }, + // electrs-esplora tcp + { + host: "electrum.blockstream.info", + port: 60001, + protocol: "tcp", + }, + // FIXME: https://github.com/keep-network/tbtc-v2/issues/502 + // // electrs-esplora ssl + // { + // host: "electrum.blockstream.info", + // port: 60002, + // protocol: "ssl", + // }, + // fulcrum tcp + { + host: "testnet.aranguren.org", + port: 51001, + protocol: "tcp", + }, + // FIXME: https://github.com/keep-network/tbtc-v2/issues/502 + // fulcrum ssl + // { + // host: "testnet.aranguren.org", + // port: 51002, + // protocol: "ssl", + // }, +] + +describe("Electrum", () => { + /** + * This test suite is meant to check the behavior of the Electrum-based + * Bitcoin client implementation. This suite requires an integration with a + * real testnet Electrum server. That requirement makes those tests + * time-consuming and vulnerable to external service health fluctuations. + * Because of that, they are skipped by default and should be run only + * on demand. Worth noting this test suite does not provide full coverage + * of all Electrum client functions. The `broadcast` function is not covered + * since it requires a proper Bitcoin transaction hex for each run which is + * out of scope of this suite. The `broadcast` function was tested manually + * though. + */ + describe.skip("ElectrumClient", () => { + testnetCredentials.forEach((credentials) => { + describe(`${credentials.protocol}://${credentials.host}:${credentials.port}`, async () => { + let electrumClient: ElectrumClient + + before(async () => { + electrumClient = new ElectrumClient([credentials]) + }) + + describe("getNetwork", () => { + it("should return proper network", async () => { + const result = await electrumClient.getNetwork() + expect(result).to.be.eql(BitcoinNetwork.Testnet) + }) + }) + + describe("findAllUnspentTransactionOutputs", () => { + it("should return proper UTXOs for the given address", async () => { + const result = + await electrumClient.findAllUnspentTransactionOutputs( + testnetAddress + ) + expect(result).to.be.eql([testnetUTXO]) + }) + }) + + describe("getTransactionHistory", () => { + it("should return proper transaction history for the given address", async () => { + // https://live.blockcypher.com/btc-testnet/address/tb1qumuaw3exkxdhtut0u85latkqfz4ylgwstkdzsx + const transactions = await electrumClient.getTransactionHistory( + "tb1qumuaw3exkxdhtut0u85latkqfz4ylgwstkdzsx", + 5 + ) + + const transactionsHashes = transactions.map((t) => + t.transactionHash.toString() + ) + + expect(transactionsHashes).to.be.eql([ + "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", + "f65bc5029251f0042aedb37f90dbb2bfb63a2e81694beef9cae5ec62e954c22e", + "44863a79ce2b8fec9792403d5048506e50ffa7338191db0e6c30d3d3358ea2f6", + "4c6b33b7c0550e0e536a5d119ac7189d71e1296fcb0c258e0c115356895bc0e6", + "605edd75ae0b4fa7cfc7aae8f1399119e9d7ecc212e6253156b60d60f4925d44", + ]) + }) + }) + + describe("getTransaction", () => { + it("should return proper transaction for the given hash", async () => { + const result = await electrumClient.getTransaction( + testnetTransaction.transactionHash + ) + expect(result).to.be.eql(testnetTransaction) + }) + + // TODO: Add case when transaction doesn't exist. + }) + + describe("getRawTransaction", () => { + it("should return proper raw transaction for the given hash", async () => { + const result = await electrumClient.getRawTransaction( + testnetTransaction.transactionHash + ) + expect(result).to.be.eql(testnetRawTransaction) + }) + }) + + describe("getTransactionConfirmations", () => { + let result: number + + before(async () => { + result = await electrumClient.getTransactionConfirmations( + testnetTransaction.transactionHash + ) + }) + + it("should return value greater than 6", async () => { + // Strict comparison is not possible as the number of confirmations + // constantly grows. We just make sure it's 6+. + expect(result).to.be.greaterThan(6) + }) + + // This test depends on `latestBlockHeight` function. + it("should return proper confirmations number for the given hash", async () => { + const latestBlockHeight = await electrumClient.latestBlockHeight() + + const expectedResult = + latestBlockHeight - testnetTransactionMerkleBranch.blockHeight + + expect(result).to.be.closeTo(expectedResult, 3) + }) + }) + + describe("latestBlockHeight", () => { + let result: number + + before(async () => { + result = await electrumClient.latestBlockHeight() + }) + + it("should return value greater than 6", async () => { + // Strict comparison is not possible as the latest block height + // constantly grows. We just make sure it's bigger than 0. + expect(result).to.be.greaterThan(0) + }) + + // This test depends on fetching the expected latest block height from Blockstream API. + // It can fail if Blockstream API is down or if Blockstream API or if + // Electrum Server used in tests is out-of-sync with the Blockstream API. + it("should return proper latest block height", async () => { + const expectedResult = await getExpectedLatestBlockHeight() + + expect(result).to.be.closeTo(expectedResult, 3) + }) + }) + + describe("getHeadersChain", () => { + it("should return proper headers chain", async () => { + const result = await electrumClient.getHeadersChain( + testnetHeadersChain.blockHeight, + testnetHeadersChain.headersChainLength + ) + expect(result).to.be.eql(testnetHeadersChain.headersChain) + }) + }) + + describe("getTransactionMerkle", () => { + it("should return proper transaction merkle", async () => { + const result = await electrumClient.getTransactionMerkle( + testnetTransaction.transactionHash, + testnetTransactionMerkleBranch.blockHeight + ) + expect(result).to.be.eql(testnetTransactionMerkleBranch) + }) + }) + + describe("computeElectrumScriptHash", () => { + it("should convert Bitcoin script to an Electrum script hash correctly", () => { + const script = "00144b47c798d12edd17dfb4ea98e5447926f664731c" + const expectedScriptHash = + "cabdea0bfc10fb3521721dde503487dd1f0e41dd6609da228066757563f292ab" + + expect(computeElectrumScriptHash(script)).to.be.equal( + expectedScriptHash + ) + }) + }) + }) + }) + + describe("fallback connection", async () => { + let electrumClient: ElectrumClient + + before(async () => { + electrumClient = new ElectrumClient( + [ + // Invalid server + { + host: "non-existing-host.local", + port: 50001, + protocol: "tcp", + }, + // Valid server + testnetCredentials[0], + ], + undefined, + 2, + 1000, + 2000 + ) + }) + + it("should establish connection with a fallback server", async () => { + const result = await electrumClient.getNetwork() + expect(result).to.be.eql(BitcoinNetwork.Testnet) + }) + }) + }) +}) + +/** + * Gets the height of the last block fetched from the Blockstream API. + * @returns Height of the last block. + */ +function getExpectedLatestBlockHeight(): Promise { + return new Promise((resolve, reject) => { + https + .get(`${BLOCKSTREAM_TESTNET_API_URL}/blocks/tip/height`, (resp) => { + let data = "" + + // A chunk of data has been received. + resp.on("data", (chunk) => { + data += chunk + }) + + // The whole response has been received. Print out the result. + resp.on("end", () => { + resolve(JSON.parse(data)) + }) + }) + .on("error", (err) => { + reject(err) + }) + }) +} diff --git a/typescript/test/ethereum.test.ts b/typescript/test/lib/ethereum.test.ts similarity index 99% rename from typescript/test/ethereum.test.ts rename to typescript/test/lib/ethereum.test.ts index 45e248a0f..727c273b3 100644 --- a/typescript/test/ethereum.test.ts +++ b/typescript/test/lib/ethereum.test.ts @@ -5,7 +5,7 @@ import { EthereumBridge, EthereumTBTCToken, Hex, -} from "../src" +} from "../../src" import { deployMockContract, MockContract, @@ -21,7 +21,7 @@ import { waffleChai } from "@ethereum-waffle/chai" chai.use(waffleChai) describe("Ethereum", () => { - describe("Bridge", () => { + describe("EthereumBridge", () => { let walletRegistry: MockContract let bridgeContract: MockContract let bridgeHandle: EthereumBridge @@ -490,7 +490,7 @@ describe("Ethereum", () => { ) } - describe("TBTCToken", () => { + describe("EthereumTBTCToken", () => { let tbtcToken: MockContract let tokenHandle: EthereumTBTCToken const signer: Wallet = new MockProvider().getWallets()[0] diff --git a/typescript/test/lib/utils.test.ts b/typescript/test/lib/utils.test.ts new file mode 100644 index 000000000..563fbbfda --- /dev/null +++ b/typescript/test/lib/utils.test.ts @@ -0,0 +1,181 @@ +import { assert } from "chai" +import { Hex } from "../../src" + +describe("Utils", () => { + describe("Hex", () => { + const stringUnprefixed = + "6e30e6da7a7881a70de2a1cf67b77f2f643e58eb0d055b0f5ef85802a9167727" + const stringPrefixed = + "0x6e30e6da7a7881a70de2a1cf67b77f2f643e58eb0d055b0f5ef85802a9167727" + const stringReversed = + "277716a90258f85e0f5b050deb583e642f7fb767cfa1e20da781787adae6306e" + + const validInputTests = [ + { + name: "unprefixed string", + input: stringUnprefixed, + expectedString: stringUnprefixed, + expectedPrefixedString: stringPrefixed, + }, + { + name: "prefixed string", + input: stringPrefixed, + expectedString: stringUnprefixed, + expectedPrefixedString: stringPrefixed, + }, + { + name: "prefixed uppercase string", + input: stringPrefixed.toUpperCase(), + expectedString: stringUnprefixed, + expectedPrefixedString: stringPrefixed, + }, + { + name: "string with leading and trailing zeros", + input: "00000a12d073c00000", + expectedString: "00000a12d073c00000", + expectedPrefixedString: "0x00000a12d073c00000", + }, + { + name: "empty string", + input: "", + expectedString: "", + expectedPrefixedString: "", + }, + { + name: "unprefixed buffer", + input: Buffer.from(stringUnprefixed, "hex"), + expectedString: stringUnprefixed, + expectedPrefixedString: stringPrefixed, + }, + { + name: "unprefixed uppercase buffer", + input: Buffer.from(stringUnprefixed.toUpperCase(), "hex"), + expectedString: stringUnprefixed, + expectedPrefixedString: stringPrefixed, + }, + { + name: "empty buffer", + input: Buffer.from("", "hex"), + expectedString: "", + expectedPrefixedString: "", + }, + ] + + const invalidInputTests = [ + { + name: "string with a character out of 0-9,a-z,A-Z", + input: "3a9f5G", + expectedErrorMessage: "invalid format of hex string", + }, + { + name: "string of odd length", + input: "ab12345", + expectedErrorMessage: "invalid length of hex string: 7", + }, + ] + + validInputTests.forEach( + ({ name, input, expectedString, expectedPrefixedString }) => { + context(`with input as ${name}`, () => { + const hex = Hex.from(input) + + describe("`${hex}`", () => { + it("should output expected string", () => { + const actual = `${hex}` + assert.equal(actual, expectedString) + }) + }) + + describe("toString", () => { + it("should output expected string", () => { + const actual = hex.toString() + assert.equal(actual, expectedString) + }) + }) + + describe("toPrefixedString", () => { + it("should output expected string", () => { + const actual = hex.toPrefixedString() + assert.equal(actual, expectedPrefixedString) + }) + }) + }) + } + ) + + invalidInputTests.forEach(({ name, input, expectedErrorMessage }) => { + context(`with input as ${name}`, () => { + it(`should throw error with message: ${expectedErrorMessage}`, () => { + assert.throws( + () => { + Hex.from(input) + }, + Error, + expectedErrorMessage + ) + }) + }) + }) + + describe("reverse", () => { + const hex = Hex.from(stringPrefixed) + const hexReversed = hex.reverse() + + it("should not modify source hex", () => { + assert.equal(hex.toString(), stringUnprefixed) + }) + + it("should reverse target hex", () => { + assert.equal(hexReversed.toString(), stringReversed) + }) + }) + + describe("toBuffer", () => { + const hex = Hex.from(stringPrefixed) + const expectedBuffer = Buffer.from(stringUnprefixed, "hex") + + it("should output a buffer", () => { + assert.deepEqual(hex.toBuffer(), expectedBuffer) + }) + + it("should not modify source hex when target buffer is changed", () => { + const buffer = hex.toBuffer() + buffer.reverse() + + assert.equal(hex.toString(), stringUnprefixed) + }) + }) + + describe("equals", () => { + const hexLowerCased = Hex.from(stringPrefixed.toLowerCase()) + const hexUpperCased = Hex.from(stringPrefixed.toUpperCase()) + + context("for the same values with matching cases", () => { + it("should return true", () => { + assert.isTrue(hexUpperCased.equals(hexUpperCased)) + }) + }) + + context("for the same values but not matching cases", () => { + it("should return true", () => { + assert.isTrue(hexLowerCased.equals(hexUpperCased)) + }) + }) + + context("for the same value but prefixed and unprefixed", () => { + it("should return true", () => { + assert.isTrue( + Hex.from(stringPrefixed).equals(Hex.from(stringUnprefixed)) + ) + }) + }) + + context("for different values", () => { + it("should return false", () => { + const otherValue: Hex = Hex.from(stringPrefixed.slice(-2)) + assert.isFalse(hexLowerCased.equals(otherValue)) + }) + }) + }) + }) +}) diff --git a/typescript/test/proof.test.ts b/typescript/test/proof.test.ts deleted file mode 100644 index fc52d7bc2..000000000 --- a/typescript/test/proof.test.ts +++ /dev/null @@ -1,394 +0,0 @@ -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import { - BitcoinHeaderSerializer, - BitcoinTx, - BitcoinHeader, - BitcoinSpvProof, - assembleBitcoinSpvProof, - validateBitcoinSpvProof, - Hex, -} from "../src" -import { - singleInputProofTestData, - multipleInputsProofTestData, - transactionConfirmationsInOneEpochData, - transactionConfirmationsInTwoEpochsData, - testnetTransactionData, - ProofTestData, - TransactionProofData, -} from "./data/proof" -import { expect } from "chai" -import * as chai from "chai" -import chaiAsPromised from "chai-as-promised" -chai.use(chaiAsPromised) - -describe("Proof", () => { - describe("assembleTransactionProof", () => { - let bitcoinClient: MockBitcoinClient - - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - }) - - context("when the transaction has one input", () => { - let proof: BitcoinTx & BitcoinSpvProof - - beforeEach(async () => { - proof = await runProofScenario(singleInputProofTestData) - }) - - it("should return the correct value of the proof", async () => { - const expectedProof = singleInputProofTestData.expectedProof - expect(proof.transactionHash).to.be.deep.equal( - expectedProof.transactionHash - ) - expect(proof.inputs).to.deep.equal(expectedProof.inputs) - expect(proof.outputs).to.deep.equal(expectedProof.outputs) - expect(proof.merkleProof).to.equal(expectedProof.merkleProof) - expect(proof.txIndexInBlock).to.equal(expectedProof.txIndexInBlock) - expect(proof.bitcoinHeaders).to.equal(expectedProof.bitcoinHeaders) - }) - }) - - context("when the transaction has multiple inputs", () => { - let proof: BitcoinTx & BitcoinSpvProof - - beforeEach(async () => { - proof = await runProofScenario(multipleInputsProofTestData) - }) - - it("should return the correct value of the proof", async () => { - const expectedProof = multipleInputsProofTestData.expectedProof - expect(proof.transactionHash).to.deep.equal( - expectedProof.transactionHash - ) - expect(proof.inputs).to.deep.equal(expectedProof.inputs) - expect(proof.outputs).to.deep.equal(expectedProof.outputs) - expect(proof.merkleProof).to.equal(expectedProof.merkleProof) - expect(proof.txIndexInBlock).to.equal(expectedProof.txIndexInBlock) - expect(proof.bitcoinHeaders).to.equal(expectedProof.bitcoinHeaders) - }) - }) - - context("when the transaction does not have enough confirmations", () => { - let notEnoughConfirmationsSweepProofTestData: ProofTestData - - beforeEach(async () => { - notEnoughConfirmationsSweepProofTestData = singleInputProofTestData - notEnoughConfirmationsSweepProofTestData.bitcoinChainData.accumulatedTxConfirmations = 5 - }) - - it("should revert", async () => { - await expect( - runProofScenario(notEnoughConfirmationsSweepProofTestData) - ).to.be.rejectedWith( - "Transaction confirmations number[5] is not enough, required [6]" - ) - }) - }) - - async function runProofScenario( - data: ProofTestData - ): Promise { - const transactions = new Map() - const transactionHash = data.bitcoinChainData.transaction.transactionHash - transactions.set( - transactionHash.toString(), - data.bitcoinChainData.transaction - ) - bitcoinClient.transactions = transactions - bitcoinClient.latestHeight = data.bitcoinChainData.latestBlockHeight - bitcoinClient.headersChain = data.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = - data.bitcoinChainData.transactionMerkleBranch - const confirmations = new Map() - confirmations.set( - transactionHash.toString(), - data.bitcoinChainData.accumulatedTxConfirmations - ) - bitcoinClient.confirmations = confirmations - - const proof = await assembleBitcoinSpvProof( - transactionHash, - data.requiredConfirmations, - bitcoinClient - ) - - return proof - } - }) - - describe("validateTransactionProof", () => { - let bitcoinClient: MockBitcoinClient - - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - }) - - context("when the transaction proof is correct", () => { - context("when the transaction is from Bitcoin Mainnet", () => { - context( - "when the transaction confirmations span only one epoch", - () => { - it("should not throw", async () => { - await expect( - runProofValidationScenario( - transactionConfirmationsInOneEpochData - ) - ).not.to.be.rejected - }) - } - ) - - context("when the transaction confirmations span two epochs", () => { - it("should not throw", async () => { - await expect( - runProofValidationScenario( - transactionConfirmationsInTwoEpochsData - ) - ).not.to.be.rejected - }) - }) - }) - - context("when the transaction is from Bitcoin Testnet", () => { - it("should not throw", async () => { - await expect(runProofValidationScenario(testnetTransactionData)).not - .to.be.rejected - }) - }) - }) - - context("when the transaction proof is incorrect", () => { - context("when the length of headers chain is incorrect", () => { - it("should throw", async () => { - // Corrupt data by adding additional byte to the headers chain. - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - headersChain: - transactionConfirmationsInOneEpochData.bitcoinChainData - .headersChain + "ff", - }, - } - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith("Incorrect length of Bitcoin headers") - }) - }) - - context( - "when the headers chain contains an incorrect number of headers", - () => { - // Corrupt the data by adding additional 80 bytes to the headers chain. - it("should throw", async () => { - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - headersChain: - transactionConfirmationsInOneEpochData.bitcoinChainData - .headersChain + "f".repeat(160), - }, - } - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith("Wrong number of confirmations") - }) - } - ) - - context("when the merkle proof is of incorrect length", () => { - it("should throw", async () => { - // Corrupt the data by adding a byte to the Merkle proof. - const merkle = [ - ...transactionConfirmationsInOneEpochData.bitcoinChainData - .transactionMerkleBranch.merkle, - ] - merkle[merkle.length - 1] += "ff" - - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - transactionMerkleBranch: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData - .transactionMerkleBranch, - merkle: merkle, - }, - }, - } - - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith("Incorrect length of Merkle proof") - }) - }) - - context("when the merkle proof is empty", () => { - it("should throw", async () => { - // Corrupt the data by making the Merkle proof empty. - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - transactionMerkleBranch: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData - .transactionMerkleBranch, - merkle: [], - }, - }, - } - - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith("Invalid merkle tree") - }) - }) - - context("when the merkle proof contains incorrect hash", () => { - it("should throw", async () => { - // Corrupt the data by changing a byte of one of the hashes in the - // Merkle proof. - const merkle = [ - ...transactionConfirmationsInOneEpochData.bitcoinChainData - .transactionMerkleBranch.merkle, - ] - - merkle[3] = "ff" + merkle[3].slice(2) - - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - transactionMerkleBranch: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData - .transactionMerkleBranch, - merkle: merkle, - }, - }, - } - - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith( - "Transaction Merkle proof is not valid for provided header and transaction hash" - ) - }) - }) - - context("when the block headers do not form a chain", () => { - it("should throw", async () => { - // Corrupt data by modifying previous block header hash of one of the - // headers. - const headers: BitcoinHeader[] = - BitcoinHeaderSerializer.deserializeHeadersChain( - transactionConfirmationsInOneEpochData.bitcoinChainData - .headersChain - ) - headers[headers.length - 1].previousBlockHeaderHash = Hex.from( - "ff".repeat(32) - ) - const corruptedHeadersChain: string = headers - .map(BitcoinHeaderSerializer.serializeHeader) - .join("") - - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - headersChain: corruptedHeadersChain, - }, - } - - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith("Invalid headers chain") - }) - }) - - context("when one of the block headers has insufficient work", () => { - it("should throw", async () => { - // Corrupt data by modifying the nonce of one of the headers, so that - // the resulting hash will be above the required difficulty target. - const headers: BitcoinHeader[] = - BitcoinHeaderSerializer.deserializeHeadersChain( - transactionConfirmationsInOneEpochData.bitcoinChainData - .headersChain - ) - headers[headers.length - 1].nonce++ - const corruptedHeadersChain: string = headers - .map(BitcoinHeaderSerializer.serializeHeader) - .join("") - - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInOneEpochData, - bitcoinChainData: { - ...transactionConfirmationsInOneEpochData.bitcoinChainData, - headersChain: corruptedHeadersChain, - }, - } - - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith("Insufficient work in the header") - }) - }) - - context( - "when some of the block headers are not at current or previous difficulty", - () => { - it("should throw", async () => { - // Corrupt data by setting current difficulty to a different value - // than stored in block headers. - const corruptedProofData: TransactionProofData = { - ...transactionConfirmationsInTwoEpochsData, - bitcoinChainData: { - ...transactionConfirmationsInTwoEpochsData.bitcoinChainData, - currentDifficulty: - transactionConfirmationsInTwoEpochsData.bitcoinChainData.currentDifficulty.add( - 1 - ), - }, - } - - await expect( - runProofValidationScenario(corruptedProofData) - ).to.be.rejectedWith( - "Header difficulty not at current or previous Bitcoin difficulty" - ) - }) - } - ) - }) - - async function runProofValidationScenario(data: TransactionProofData) { - const transactions = new Map() - const transactionHash = data.bitcoinChainData.transaction.transactionHash - transactions.set( - transactionHash.toString(), - data.bitcoinChainData.transaction - ) - bitcoinClient.transactions = transactions - bitcoinClient.latestHeight = data.bitcoinChainData.latestBlockHeight - bitcoinClient.headersChain = data.bitcoinChainData.headersChain - bitcoinClient.transactionMerkle = - data.bitcoinChainData.transactionMerkleBranch - const confirmations = new Map() - confirmations.set( - transactionHash.toString(), - data.bitcoinChainData.accumulatedTxConfirmations - ) - bitcoinClient.confirmations = confirmations - - await validateBitcoinSpvProof( - data.bitcoinChainData.transaction.transactionHash, - data.requiredConfirmations, - data.bitcoinChainData.previousDifficulty, - data.bitcoinChainData.currentDifficulty, - bitcoinClient - ) - } - }) -}) diff --git a/typescript/test/deposit.test.ts b/typescript/test/services/deposits.test.ts similarity index 79% rename from typescript/test/deposit.test.ts rename to typescript/test/services/deposits.test.ts index 73f33e320..821228f8c 100644 --- a/typescript/test/deposit.test.ts +++ b/typescript/test/services/deposits.test.ts @@ -6,7 +6,7 @@ import { testnetTransaction, testnetTransactionHash, testnetUTXO, -} from "./data/deposit" +} from "../data/deposit" import { BitcoinLocktimeUtils, BitcoinNetwork, @@ -16,15 +16,22 @@ import { Deposit, DepositFunding, DepositReceipt, + DepositRefund, DepositScript, EthereumAddress, extractBitcoinRawTxVectors, -} from "../src" -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" -import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" -import { txToJSON } from "./utils/helpers" +} from "../../src" +import { MockBitcoinClient } from "../utils/mock-bitcoin-client" +import { MockTBTCContracts } from "../utils/mock-tbtc-contracts" +import { txToJSON } from "../utils/helpers" +import { + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress, + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress, + depositRefundOfWitnessDepositAndWitnessRefunderAddress, + refunderPrivateKey, +} from "../data/deposit-refund" -describe("Deposit", () => { +describe("Deposits", () => { const depositCreatedAt: number = 1640181600 const depositRefundLocktimeDuration: number = 2592000 @@ -723,4 +730,172 @@ describe("Deposit", () => { describe("DepositsService", () => { // TODO: Implement unit tests. }) + + describe("DepositRefund", () => { + const fee = BigNumber.from(1520) + + describe("DepositRefund", () => { + describe("submitTransaction", () => { + let bitcoinClient: MockBitcoinClient + + beforeEach(async () => { + bitcoinClient = new MockBitcoinClient() + }) + + context( + "when the refund transaction is requested to be witness", + () => { + context("when the refunded deposit was witness", () => { + let transactionHash: BitcoinTxHash + + beforeEach(async () => { + const utxo = + depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit + .utxo + const deposit = + depositRefundOfWitnessDepositAndWitnessRefunderAddress.deposit + .data + const refunderAddress = + depositRefundOfWitnessDepositAndWitnessRefunderAddress.refunderAddress + const refunderPrivateKey = + "cTWhf1nXc7aW8BN2qLtWcPtcgcWYKfzRXkCJNsuQ86HR8uJBYfMc" + + const rawTransactions = new Map() + rawTransactions.set(utxo.transactionHash.toString(), { + transactionHex: utxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + + const depositRefund = DepositRefund.fromScript( + DepositScript.fromReceipt(deposit) + ) + + ;({ transactionHash } = await depositRefund.submitTransaction( + bitcoinClient, + fee, + utxo, + refunderAddress, + refunderPrivateKey + )) + }) + + it("should broadcast refund transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositRefundOfWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositRefundOfWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transactionHash + ) + }) + }) + + context("when the refunded deposit was non-witness", () => { + let transactionHash: BitcoinTxHash + + beforeEach(async () => { + const utxo = + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress + .deposit.utxo + const deposit = + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress + .deposit.data + const refunderAddress = + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress.refunderAddress + + const rawTransactions = new Map() + rawTransactions.set(utxo.transactionHash.toString(), { + transactionHex: utxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + + const depositRefund = DepositRefund.fromScript( + DepositScript.fromReceipt(deposit) + ) + + ;({ transactionHash } = await depositRefund.submitTransaction( + bitcoinClient, + fee, + utxo, + refunderAddress, + refunderPrivateKey + )) + }) + + it("should broadcast refund transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositRefundOfNonWitnessDepositAndWitnessRefunderAddress + .expectedRefund.transactionHash + ) + }) + }) + } + ) + + context( + "when the refund transaction is requested to be non-witness", + () => { + let transactionHash: BitcoinTxHash + + beforeEach(async () => { + const utxo = + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress + .deposit.utxo + const deposit = + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress + .deposit.data + const refunderAddress = + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress.refunderAddress + + const rawTransactions = new Map() + rawTransactions.set(utxo.transactionHash.toString(), { + transactionHex: utxo.transactionHex, + }) + + const depositRefund = DepositRefund.fromScript( + DepositScript.fromReceipt(deposit) + ) + + bitcoinClient.rawTransactions = rawTransactions + ;({ transactionHash } = await depositRefund.submitTransaction( + bitcoinClient, + fee, + utxo, + refunderAddress, + refunderPrivateKey + )) + }) + + it("should broadcast refund transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress + .expectedRefund.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositRefundOfWitnessDepositAndNonWitnessRefunderAddress + .expectedRefund.transactionHash + ) + }) + } + ) + }) + }) + }) }) diff --git a/typescript/test/redemption.test.ts b/typescript/test/services/maintenance.test.ts similarity index 59% rename from typescript/test/redemption.test.ts rename to typescript/test/services/maintenance.test.ts index 4fbd9f5fe..cbca52a78 100644 --- a/typescript/test/redemption.test.ts +++ b/typescript/test/services/maintenance.test.ts @@ -1,23 +1,28 @@ +import { BigNumber, BigNumberish } from "ethers" +import { MockTBTCContracts } from "../utils/mock-tbtc-contracts" +import { MockBitcoinClient } from "../utils/mock-bitcoin-client" import { - BitcoinAddressConverter, - BitcoinHashUtils, BitcoinNetwork, BitcoinRawTx, BitcoinTx, BitcoinTxHash, BitcoinUtxo, - Hex, MaintenanceService, - NewWalletRegisteredEvent, RedemptionRequest, - RedemptionsService, - Wallet, - WalletState, WalletTx, -} from "../src" -import { MockBitcoinClient } from "./utils/mock-bitcoin-client" +} from "../../src" +import { + depositSweepProof, + depositSweepWithNoMainUtxoAndNonWitnessOutput, + depositSweepWithNoMainUtxoAndWitnessOutput, + depositSweepWithNonWitnessMainUtxoAndWitnessOutput, + depositSweepWithWitnessMainUtxoAndWitnessOutput, + NO_MAIN_UTXO, +} from "../data/deposit-sweep" +import { testnetWalletAddress, testnetWalletPrivateKey } from "../data/deposit" +import { expect } from "chai" +import { txToJSON } from "../utils/helpers" import { - findWalletForRedemptionData, multipleRedemptionsWithoutChange, multipleRedemptionsWithWitnessChange, p2pkhWalletAddress, @@ -31,787 +36,1052 @@ import { singleP2WSHRedemptionWithWitnessChange, walletPrivateKey, walletPublicKey, -} from "./data/redemption" -import { MockBridge } from "./utils/mock-bridge" -import * as chai from "chai" -import { expect } from "chai" -import chaiAsPromised from "chai-as-promised" -import { BigNumber, BigNumberish } from "ethers" -import { MockTBTCContracts } from "./utils/mock-tbtc-contracts" -import { txToJSON } from "./utils/helpers" - -chai.use(chaiAsPromised) - -describe("Redemption", () => { - describe("RedemptionsService", () => { - describe("requestRedemption", () => { - const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange - const { transactionHash, value } = data.mainUtxo - const mainUtxo: BitcoinUtxo = { - transactionHash, - outputIndex: 0, - value, - } - const redeemerOutputScript = - data.pendingRedemptions[0].pendingRedemption.redeemerOutputScript - const amount = - data.pendingRedemptions[0].pendingRedemption.requestedAmount +} from "../data/redemption" +import { runRedemptionScenario } from "./redemptions.test" - let tbtcContracts: MockTBTCContracts - let bitcoinClient: MockBitcoinClient +describe("Maintenance", () => { + describe("WalletTx", () => { + describe("DepositSweep", () => { + const fee = BigNumber.from(1600) - beforeEach(async () => { - tbtcContracts = new MockTBTCContracts() - bitcoinClient = new MockBitcoinClient() + describe("submitTransaction", () => { + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient - const walletPublicKeyHash = Hex.from( - BitcoinHashUtils.computeHash160(walletPublicKey) - ) + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + }) - // Prepare NewWalletRegisteredEvent history. Set only relevant fields. - tbtcContracts.bridge.newWalletRegisteredEvents = [ - { - walletPublicKeyHash: walletPublicKeyHash, - } as NewWalletRegisteredEvent, - ] - - // Prepare wallet data in the Bridge. Set only relevant fields. - tbtcContracts.bridge.setWallet(walletPublicKeyHash.toPrefixedString(), { - state: WalletState.Live, - walletPublicKey: Hex.from(walletPublicKey), - pendingRedemptionsValue: BigNumber.from(0), - mainUtxoHash: tbtcContracts.bridge.buildUtxoHash(mainUtxo), - } as Wallet) - - const walletAddress = BitcoinAddressConverter.publicKeyHashToAddress( - walletPublicKeyHash.toString(), - true, - BitcoinNetwork.Testnet - ) + context("when the new main UTXO is requested to be witness", () => { + context( + "when there is no main UTXO from previous deposit sweep", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo - // Prepare wallet transaction history for main UTXO lookup. - // Set only relevant fields. - const transactionHistory = new Map() - transactionHistory.set(walletAddress, [ - { - transactionHash: mainUtxo.transactionHash, - outputs: [ - { - outputIndex: mainUtxo.outputIndex, - value: mainUtxo.value, - scriptPubKey: BitcoinAddressConverter.addressToOutputScript( - walletAddress, - BitcoinNetwork.Testnet - ), - }, - ], - } as BitcoinTx, - ]) - bitcoinClient.transactionHistory = transactionHistory - - const redemptionsService = new RedemptionsService( - tbtcContracts, - bitcoinClient - ) + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithNoMainUtxoAndWitnessOutput.deposits) { + rawTransactions.set(deposit.utxo.transactionHash.toString(), { + transactionHex: deposit.utxo.transactionHex, + }) + } + bitcoinClient.rawTransactions = rawTransactions - await redemptionsService.requestRedemption( - BitcoinAddressConverter.outputScriptToAddress( - Hex.from(redeemerOutputScript), - BitcoinNetwork.Testnet - ), - amount - ) - }) + const utxos: BitcoinUtxo[] = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (data) => { + return data.utxo + } + ) - it("should submit redemption request with correct arguments", () => { - const tokenLog = tbtcContracts.tbtcToken.requestRedemptionLog + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + const witness = + depositSweepWithNoMainUtxoAndWitnessOutput.witness - expect(tokenLog.length).to.equal(1) - expect(tokenLog[0]).to.deep.equal({ - walletPublicKey, - mainUtxo, - redeemerOutputScript, - amount, + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposit + )) + }) + + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash, + outputIndex: 0, + value: BigNumber.from(35400), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context("when there is main UTXO from previous deposit sweep", () => { + context( + "when main UTXO from previous deposit sweep is witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits) { + rawTransactions.set( + deposit.utxo.transactionHash.toString(), + { + transactionHex: deposit.utxo.transactionHex, + } + ) + } + // The main UTXO resulting from another data set was used as input. + // Set raw data of that main UTXO as well. + rawTransactions.set( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString(), + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transaction + ) + bitcoinClient.rawTransactions = rawTransactions + + const utxos: BitcoinUtxo[] = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + const witness = + depositSweepWithWitnessMainUtxoAndWitnessOutput.witness + + const mainUtxo = + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposit, + mainUtxo + )) + }) + + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(60800), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context( + "when main UTXO from previous deposit sweep is non-witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits) { + rawTransactions.set( + deposit.utxo.transactionHash.toString(), + { + transactionHex: deposit.utxo.transactionHex, + } + ) + } + rawTransactions.set( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString(), + { + transactionHex: + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .mainUtxo.transactionHex, + } + ) + bitcoinClient.rawTransactions = rawTransactions + + const utxos: BitcoinUtxo[] = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + const witness = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness + + const mainUtxo = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposit, + mainUtxo + )) + }) + + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(33800), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + }) }) - }) - }) - describe("getRedemptionRequest", () => { - context("when asked for a pending request", () => { - const { redemptionKey, pendingRedemption: redemptionRequest } = - multipleRedemptionsWithWitnessChange.pendingRedemptions[0] + context("when the new main UTXO is requested to be non-witness", () => { + // The only difference between deposit sweep transactions with witness and + // non-witness output is the output type itself. + // Therefore only one test case was added for non-witness transactions. + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + + beforeEach(async () => { + // Map transaction hashes for UTXOs to transactions in hexadecimal and + // set the mapping in the mock Bitcoin client + const rawTransactions = new Map() + for (const deposit of depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits) { + rawTransactions.set(deposit.utxo.transactionHash.toString(), { + transactionHex: deposit.utxo.transactionHex, + }) + } + bitcoinClient.rawTransactions = rawTransactions + + const utxos = + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( + (data) => { + return data.utxo + } + ) + + const deposits = + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + const witness = + depositSweepWithNoMainUtxoAndNonWitnessOutput.witness + + const walletTx = new WalletTx(tbtcContracts, bitcoinClient, witness) + + ;({ transactionHash, newMainUtxo } = + await walletTx.depositSweep.submitTransaction( + fee, + testnetWalletPrivateKey, + utxos, + deposits + )) + }) + + it("should broadcast sweep transaction with proper structure", async () => { + expect(bitcoinClient.broadcastLog.length).to.be.equal(1) + expect(bitcoinClient.broadcastLog[0]).to.be.eql( + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep + .transaction + ) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep + .transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep + .transactionHash, + outputIndex: 0, + value: BigNumber.from(13400), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + }) + }) + describe("assembleTransaction", () => { let tbtcContracts: MockTBTCContracts let bitcoinClient: MockBitcoinClient - let redemptionsService: RedemptionsService - beforeEach(async () => { - tbtcContracts = new MockTBTCContracts() - bitcoinClient = new MockBitcoinClient() + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + }) + + context("when the new main UTXO is requested to be witness", () => { + context( + "when there is no main UTXO from previous deposit sweep", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (data) => { + return data.utxo + } + ) + + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit + )) + }) + + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transaction + ) + + // Convert raw transaction to JSON to make detailed comparison. + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) + + expect(txJSON.hash).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(2) + + const p2shInput = txJSON.inputs[0] + expect(p2shInput.hash).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2shInput.index).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo + .outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2shInput.witness).to.be.empty + expect(p2shInput.script.length).to.be.greaterThan(0) + + const p2wshInput = txJSON.inputs[1] + expect(p2wshInput.hash).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() + ) + expect(p2wshInput.index).to.be.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[1].utxo + .outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) + const sweepOutput = txJSON.outputs[0] + + // Should be OP_0 . Public key corresponds to the + // wallet BTC address. + expect(sweepOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + // The output's value should be equal to the sum of all input values + // minus fee (25000 + 12000 - 1600) + expect(sweepOutput.value).to.be.equal(35400) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNoMainUtxoAndWitnessOutput.expectedSweep + .transactionHash, + outputIndex: 0, + value: BigNumber.from(35400), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context("when there is main UTXO from previous deposit sweep", () => { + context( + "when main UTXO prom previous deposit sweep is witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + // P2WPKH + const mainUtxoWithRaw = + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + + const witness = + depositSweepWithWitnessMainUtxoAndWitnessOutput.witness + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit, + mainUtxoWithRaw + )) + }) + + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + + // Convert raw transaction to JSON to make detailed comparison. + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) + + expect(txJSON.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(3) + + const p2wkhInput = txJSON.inputs[0] + expect(p2wkhInput.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() + ) + expect(p2wkhInput.index).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.mainUtxo + .outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wkhInput.witness.length).to.be.greaterThan(0) + expect(p2wkhInput.script.length).to.be.equal(0) + + const p2shInput = txJSON.inputs[1] + expect(p2shInput.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2shInput.index).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[0] + .utxo.outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2shInput.witness).to.be.empty + expect(p2shInput.script.length).to.be.greaterThan(0) + + const p2wshInput = txJSON.inputs[2] + expect(p2wshInput.hash).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1].utxo.transactionHash.toString() + ) + expect(p2wshInput.index).to.be.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput.deposits[1] + .utxo.outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) + + const sweepOutput = txJSON.outputs[0] + // Should be OP_0 . Public key corresponds to the + // wallet BTC address. + expect(sweepOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + // The output's value should be equal to the sum of all input values + // minus fee (17000 + 10000 + 35400 - 1600) + expect(sweepOutput.value).to.be.equal(60800) + }) + + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) + + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(60800), + } + + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } + ) + + context( + "when main UTXO from previous deposit sweep is non-witness", + () => { + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) + + const deposit = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) + + // P2WPKH + const mainUtxoWithRaw = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + + const witness = + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.witness + + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) + + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit, + mainUtxoWithRaw + )) + }) + + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transaction + ) + + // Convert raw transaction to JSON to make detailed comparison. + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet + ) + + expect(txJSON.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) + + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(2) + + const p2pkhInput = txJSON.inputs[0] // main UTXO + expect(p2pkhInput.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo.transactionHash.toString() + ) + expect(p2pkhInput.index).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.mainUtxo + .outputIndex + ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2pkhInput.witness).to.be.empty + expect(p2pkhInput.script.length).to.be.greaterThan(0) + + const p2wshInput = txJSON.inputs[1] + expect(p2wshInput.hash).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2wshInput.index).to.be.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .deposits[0].utxo.outputIndex + ) + // Transaction should be signed. As it's a SegWit input, the `witness` + // field should be filled, while the `script` field should be empty. + expect(p2wshInput.witness.length).to.be.greaterThan(0) + expect(p2wshInput.script.length).to.be.equal(0) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) - const pendingRedemptions = new Map() - pendingRedemptions.set(redemptionKey, redemptionRequest) + const sweepOutput = txJSON.outputs[0] + // Should be OP_0 . Public key corresponds to the + // wallet BTC address. + expect(sweepOutput.script).to.be.equal( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal(testnetWalletAddress) + // The output's value should be equal to the sum of all input values + // minus fee (16400 + 19000 - 1600) + expect(sweepOutput.value).to.be.equal(33800) + }) - tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash + ) + }) - redemptionsService = new RedemptionsService( - tbtcContracts, - bitcoinClient - ) - }) + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNonWitnessMainUtxoAndWitnessOutput + .expectedSweep.transactionHash, + outputIndex: 0, + value: BigNumber.from(33800), + } - it("should return the expected redemption request", async () => { - const actualRedemptionRequest = - await redemptionsService.getRedemptionRequests( - BitcoinAddressConverter.outputScriptToAddress( - Hex.from(redemptionRequest.redeemerOutputScript), - BitcoinNetwork.Testnet - ), - walletPublicKey, - "pending" + expect(newMainUtxo).to.be.eql(expectedNewMainUtxo) + }) + } ) - - expect(actualRedemptionRequest).to.be.eql(redemptionRequest) + }) }) - }) - context("when asked for a timed-out request", () => { - const { redemptionKey, pendingRedemption: redemptionRequest } = - multipleRedemptionsWithWitnessChange.pendingRedemptions[0] + context("when the new main UTXO is requested to be non-witness", () => { + // The only difference between deposit sweep transactions with witness and + // non-witness output is the output type itself. + // Therefore only one test case was added for non-witness transactions. + let transactionHash: BitcoinTxHash + let newMainUtxo: BitcoinUtxo + let transaction: BitcoinRawTx + + const utxosWithRaw = + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( + (deposit) => { + return deposit.utxo + } + ) - let tbtcContracts: MockTBTCContracts - let bitcoinClient: MockBitcoinClient - let redemptionsService: RedemptionsService + const deposit = + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits.map( + (deposit) => { + return deposit.data + } + ) - beforeEach(async () => { - tbtcContracts = new MockTBTCContracts() - bitcoinClient = new MockBitcoinClient() + const witness = depositSweepWithNoMainUtxoAndNonWitnessOutput.witness - const timedOutRedemptions = new Map() - timedOutRedemptions.set(redemptionKey, redemptionRequest) + const walletTx = new WalletTx(tbtcContracts, bitcoinClient, witness) - tbtcContracts.bridge.setTimedOutRedemptions(timedOutRedemptions) + beforeEach(async () => { + ;({ + transactionHash, + newMainUtxo, + rawTransaction: transaction, + } = await walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit + )) + }) - redemptionsService = new RedemptionsService( - tbtcContracts, - bitcoinClient - ) - }) + it("should return sweep transaction with proper structure", () => { + // Compare HEXes. + expect(transaction).to.be.eql( + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep + .transaction + ) - it("should return the expected redemption request", async () => { - const actualRedemptionRequest = - await redemptionsService.getRedemptionRequests( - BitcoinAddressConverter.outputScriptToAddress( - Hex.from(redemptionRequest.redeemerOutputScript), - BitcoinNetwork.Testnet - ), - walletPublicKey, - "timedOut" + // Convert raw transaction to JSON to make detailed comparison. + const txJSON = txToJSON( + transaction.transactionHex, + BitcoinNetwork.Testnet ) - expect(actualRedemptionRequest).to.be.eql(redemptionRequest) - }) - }) - }) + expect(txJSON.hash).to.be.equal( + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep.transactionHash.toString() + ) + expect(txJSON.version).to.be.equal(1) - describe("findWalletForRedemption", () => { - class TestRedemptionsService extends RedemptionsService { - public async findWalletForRedemption( - redeemerOutputScript: string, - amount: BigNumber - ): Promise<{ - walletPublicKey: string - mainUtxo: BitcoinUtxo - }> { - return super.findWalletForRedemption(redeemerOutputScript, amount) - } - } + // Validate inputs. + expect(txJSON.inputs.length).to.be.equal(1) - let tbtcContracts: MockTBTCContracts - let bitcoinClient: MockBitcoinClient - let redemptionsService: TestRedemptionsService - // script for testnet P2WSH address - // tb1qau95mxzh2249aa3y8exx76ltc2sq0e7kw8hj04936rdcmnynhswqqz02vv - const redeemerOutputScript = - "0x220020ef0b4d985752aa5ef6243e4c6f6bebc2a007e7d671ef27d4b1d0db8dcc93bc1c" - - context( - "when there are no wallets in the network that can handle redemption", - () => { - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - beforeEach(() => { - bitcoinClient = new MockBitcoinClient() - tbtcContracts = new MockTBTCContracts() - tbtcContracts.bridge.newWalletRegisteredEvents = [] - redemptionsService = new TestRedemptionsService( - tbtcContracts, - bitcoinClient + const p2shInput = txJSON.inputs[0] + expect(p2shInput.hash).to.be.equal( + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo.transactionHash.toString() + ) + expect(p2shInput.index).to.be.equal( + depositSweepWithNoMainUtxoAndNonWitnessOutput.deposits[0].utxo + .outputIndex ) + // Transaction should be signed. As it's not SegWit input, the `witness` + // field should be empty, while the `script` field should be filled. + expect(p2shInput.witness).to.be.empty + expect(p2shInput.script.length).to.be.greaterThan(0) + + // Validate outputs. + expect(txJSON.outputs.length).to.be.equal(1) + + const sweepOutput = txJSON.outputs[0] + // OP_DUP OP_HASH160 0x14 0x8db50eb52063ea9d98b3eac91489a90f738986f6 + // OP_EQUALVERIFY OP_CHECKSIG + expect(sweepOutput.script).to.be.equal( + "76a9148db50eb52063ea9d98b3eac91489a90f738986f688ac" + ) + // The output's address should be the wallet's address + expect(sweepOutput.address).to.be.equal( + "mtSEUCE7G8om9zJttG9twtjoiSsUz7QnY9" + ) + // The output's value should be equal to the sum of all input values + // minus fee (15000- 1600) + expect(sweepOutput.value).to.be.equal(13400) }) - it("should throw an error", async () => { - await expect( - redemptionsService.findWalletForRedemption( - redeemerOutputScript, - amount - ) - ).to.be.rejectedWith( - "Currently, there are no live wallets in the network." + it("should return the proper transaction hash", async () => { + expect(transactionHash).to.be.deep.equal( + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep + .transactionHash ) }) - } - ) - - context("when there are registered wallets in the network", () => { - let result: Awaited | never> - const walletsOrder = [ - findWalletForRedemptionData.nonLiveWallet, - findWalletForRedemptionData.walletWithoutUtxo, - findWalletForRedemptionData.walletWithPendingRedemption, - findWalletForRedemptionData.liveWallet, - ] - - beforeEach(async () => { - bitcoinClient = new MockBitcoinClient() - tbtcContracts = new MockTBTCContracts() - tbtcContracts.bridge.newWalletRegisteredEvents = walletsOrder.map( - (wallet) => wallet.event - ) + it("should return the proper new main UTXO", () => { + const expectedNewMainUtxo = { + transactionHash: + depositSweepWithNoMainUtxoAndNonWitnessOutput.expectedSweep + .transactionHash, + outputIndex: 0, + value: BigNumber.from(13400), + } - const walletsTransactionHistory = new Map() - - walletsOrder.forEach((wallet) => { - const { - state, - mainUtxoHash, - walletPublicKey, - btcAddress, - transactions, - pendingRedemptionsValue, - } = wallet.data - - walletsTransactionHistory.set(btcAddress, transactions) - tbtcContracts.bridge.setWallet( - wallet.event.walletPublicKeyHash.toPrefixedString(), - { - state, - mainUtxoHash, - walletPublicKey, - pendingRedemptionsValue, - } as Wallet - ) + expect(newMainUtxo).to.be.deep.equal(expectedNewMainUtxo) }) + }) - bitcoinClient.transactionHistory = walletsTransactionHistory + context("when there are no UTXOs", () => { + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - redemptionsService = new TestRedemptionsService( - tbtcContracts, - bitcoinClient - ) + await expect( + walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + [], + [] + ) + ).to.be.rejectedWith( + "There must be at least one deposit UTXO to sweep" + ) + }) }) context( - "when there is a wallet that can handle the redemption request", + "when the numbers of UTXOs and deposit elements are not equal", () => { - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - beforeEach(async () => { - result = await redemptionsService.findWalletForRedemption( - redeemerOutputScript, - amount + const utxosWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits.map( + (data) => { + return data.utxo + } ) - }) - - it("should get all registered wallets", () => { - const bridgeQueryEventsLog = - tbtcContracts.bridge.newWalletRegisteredEventsLog - expect(bridgeQueryEventsLog.length).to.equal(1) - expect(bridgeQueryEventsLog[0]).to.deep.equal({ - options: undefined, - filterArgs: [], - }) - }) + // Add only one element to the deposit + const deposit = [ + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data, + ] - it("should return the wallet data that can handle redemption request", () => { - const expectedWalletData = - findWalletForRedemptionData.walletWithPendingRedemption.data + const witness = depositSweepWithNoMainUtxoAndWitnessOutput.witness - expect(result).to.deep.eq({ - walletPublicKey: expectedWalletData.walletPublicKey.toString(), - mainUtxo: expectedWalletData.mainUtxo, - }) - }) - } - ) + it("should revert", async () => { + const walletTx = new WalletTx( + tbtcContracts, + bitcoinClient, + witness + ) - context( - "when the redemption request amount is too large and no wallet can handle the request", - () => { - const amount = BigNumber.from("10000000000") // 1 000 BTC - const expectedMaxAmount = walletsOrder - .map((wallet) => wallet.data) - .map((wallet) => wallet.mainUtxo) - .map((utxo) => utxo.value) - .sort((a, b) => (b.gt(a) ? 0 : -1))[0] - - it("should throw an error", async () => { await expect( - redemptionsService.findWalletForRedemption( - redeemerOutputScript, - amount + walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + utxosWithRaw, + deposit ) ).to.be.rejectedWith( - `Could not find a wallet with enough funds. Maximum redemption amount is ${expectedMaxAmount.toString()} Satoshi.` + "Number of UTXOs must equal the number of deposit elements" ) }) } ) - context( - "when there is pending redemption request from a given wallet to the same address", - () => { - beforeEach(async () => { - const redeemerOutputScript = - findWalletForRedemptionData.pendingRedemption - .redeemerOutputScript - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - - const walletPublicKeyHash = - findWalletForRedemptionData.walletWithPendingRedemption.event - .walletPublicKeyHash - - const pendingRedemptions = new Map< - BigNumberish, - RedemptionRequest - >() - - const key = MockBridge.buildRedemptionKey( - walletPublicKeyHash.toString(), - redeemerOutputScript - ) - - pendingRedemptions.set( - key, - findWalletForRedemptionData.pendingRedemption - ) - tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) - - result = await redemptionsService.findWalletForRedemption( - redeemerOutputScript, - amount - ) - }) - - it("should get all registered wallets", () => { - const bridgeQueryEventsLog = - tbtcContracts.bridge.newWalletRegisteredEventsLog - - expect(bridgeQueryEventsLog.length).to.equal(1) - expect(bridgeQueryEventsLog[0]).to.deep.equal({ - options: undefined, - filterArgs: [], - }) - }) - - it("should skip the wallet for which there is a pending redemption request to the same redeemer output script and return the wallet data that can handle redemption request", () => { - const expectedWalletData = - findWalletForRedemptionData.liveWallet.data - - expect(result).to.deep.eq({ - walletPublicKey: expectedWalletData.walletPublicKey.toString(), - mainUtxo: expectedWalletData.mainUtxo, - }) - }) + context("when the main UTXO does not belong to the wallet", () => { + const utxoWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data + + // The UTXO below does not belong to the wallet + const mainUtxoWithRaw = { + transactionHash: BitcoinTxHash.from( + "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883" + ), + outputIndex: 1, + value: BigNumber.from(3933200), + transactionHex: + "0100000000010162cae24e74ad64f9f0493b09f3964908b3b3038f4924882d3d" + + "bd853b4c9bc7390100000000ffffffff02102700000000000017a914867120d5" + + "480a9cc0c11c1193fa59b3a92e852da78710043c00000000001600147ac2d937" + + "8a1c47e589dfb8095ca95ed2140d272602483045022100b70bd9b7f5d230444a" + + "542c7971bea79786b4ebde6703cee7b6ee8cd16e115ebf02204d50ea9d1ee08d" + + "e9741498c2cc64266e40d52c4adb9ef68e65aa2727cd4208b5012102ee067a02" + + "73f2e3ba88d23140a24fdb290f27bbcd0f94117a9c65be3911c5c04e00000000", } - ) - - context( - "when wallet has pending redemptions and the requested amount is greater than possible", - () => { - beforeEach(async () => { - const wallet = - findWalletForRedemptionData.walletWithPendingRedemption - const walletBTCBalance = wallet.data.mainUtxo.value - const amount: BigNumber = walletBTCBalance - .sub(wallet.data.pendingRedemptionsValue) - .add(BigNumber.from(500000)) // 0.005 BTC - - console.log("amount", amount.toString()) + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - result = await redemptionsService.findWalletForRedemption( - redeemerOutputScript, - amount + await expect( + walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + [utxoWithRaw], + [deposit], + mainUtxoWithRaw ) - }) - - it("should skip the wallet wallet with pending redemptions and return the wallet data that can handle redemption request ", () => { - const expectedWalletData = - findWalletForRedemptionData.liveWallet.data - - expect(result).to.deep.eq({ - walletPublicKey: expectedWalletData.walletPublicKey.toString(), - mainUtxo: expectedWalletData.mainUtxo, - }) - }) - } - ) + ).to.be.rejectedWith("UTXO does not belong to the wallet") + }) + }) context( - "when all active wallets has pending redemption for a given Bitcoin address", + "when the wallet private does not correspond to the wallet public key", () => { - const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC - const redeemerOutputScript = - findWalletForRedemptionData.pendingRedemption.redeemerOutputScript - - beforeEach(async () => { - const walletPublicKeyHash = - findWalletForRedemptionData.walletWithPendingRedemption.event - .walletPublicKeyHash - - const pendingRedemptions = new Map< - BigNumberish, - RedemptionRequest - >() - - const pendingRedemption1 = MockBridge.buildRedemptionKey( - walletPublicKeyHash.toString(), - redeemerOutputScript - ) - - const pendingRedemption2 = MockBridge.buildRedemptionKey( - findWalletForRedemptionData.liveWallet.event.walletPublicKeyHash.toString(), - redeemerOutputScript - ) + const utxoWithRaw = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].utxo + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data + const anotherPrivateKey = + "cRJvyxtoggjAm9A94cB86hZ7Y62z2ei5VNJHLksFi2xdnz1GJ6xt" - pendingRedemptions.set( - pendingRedemption1, - findWalletForRedemptionData.pendingRedemption - ) - - pendingRedemptions.set( - pendingRedemption2, - findWalletForRedemptionData.pendingRedemption - ) - tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) - }) + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - it("should throw an error", async () => { await expect( - redemptionsService.findWalletForRedemption( - redeemerOutputScript, - amount + walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + anotherPrivateKey, + [utxoWithRaw], + [deposit] ) ).to.be.rejectedWith( - "All live wallets in the network have the pending redemption for a given Bitcoin address. Please use another Bitcoin address." + "Wallet public key does not correspond to wallet private key" ) }) } ) - }) - }) - - describe("determineWalletMainUtxo", () => { - class TestRedemptionsService extends RedemptionsService { - public async determineWalletMainUtxo( - walletPublicKeyHash: Hex, - bitcoinNetwork: BitcoinNetwork - ): Promise { - return super.determineWalletMainUtxo( - walletPublicKeyHash, - bitcoinNetwork - ) - } - } - - // Just an arbitrary 20-byte wallet public key hash. - const walletPublicKeyHash = Hex.from( - "e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0" - ) - - // Helper function facilitating creation of mock transactions. - const mockTransaction = ( - hash: string, - outputs: Record // key: locking script, value: amount of locked satoshis - ): BitcoinTx => { - return { - transactionHash: Hex.from(hash), - inputs: [], // not relevant in this test scenario - outputs: Object.entries(outputs).map( - ([scriptPubKey, value], index) => ({ - outputIndex: index, - value: BigNumber.from(value), - scriptPubKey: Hex.from(scriptPubKey), - }) - ), - } - } - // Create a fake wallet witness transaction history that consists of 6 transactions. - const walletWitnessTransactionHistory: BitcoinTx[] = [ - mockTransaction( - "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", - { - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "4c6b33b7c0550e0e536a5d119ac7189d71e1296fcb0c258e0c115356895bc0e6", - { - "00140000000000000000000000000000000000000001": 100000, - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output - } - ), - mockTransaction( - "44863a79ce2b8fec9792403d5048506e50ffa7338191db0e6c30d3d3358ea2f6", - { - "00140000000000000000000000000000000000000001": 100000, - "00140000000000000000000000000000000000000002": 200000, - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 300000, // wallet witness output - } - ), - mockTransaction( - "f65bc5029251f0042aedb37f90dbb2bfb63a2e81694beef9cae5ec62e954c22e", - { - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "2724545276df61f43f1e92c4b9f1dd3c9109595c022dbd9dc003efbad8ded38b", - { - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214", - { - "00140000000000000000000000000000000000000001": 100000, - "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output - } - ), - ] - - // Create a fake wallet legacy transaction history that consists of 6 transactions. - const walletLegacyTransactionHistory: BitcoinTx[] = [ - mockTransaction( - "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267", - { - "00140000000000000000000000000000000000000001": 100000, - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output - } - ), - mockTransaction( - "b11bfc481b95769b8488bd661d5f61a35f7c3c757160494d63f6e04e532dfcb9", - { - "00140000000000000000000000000000000000000001": 100000, - "00140000000000000000000000000000000000000002": 200000, - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 300000, // wallet legacy output - } - ), - mockTransaction( - "7e91580d989f8541489a37431381ff9babd596111232f1bc7a1a1ba503c27dee", - { - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "5404e339ba82e6e52fcc24cb40029bed8425baa4c7f869626ef9de956186f910", - { - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94", - { - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output - "00140000000000000000000000000000000000000001": 200000, - } - ), - mockTransaction( - "00cc0cd13fc4de7a15cb41ab6d58f8b31c75b6b9b4194958c381441a67d09b08", - { - "00140000000000000000000000000000000000000001": 100000, - "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output + context("when the type of UTXO is unsupported", () => { + // Use coinbase transaction of some block + const utxoWithRaw = { + transactionHash: BitcoinTxHash.from( + "025de155e6f2ffbbf4851493e0d28dad54020db221a3f38bf63c1f65e3d3595b" + ), + outputIndex: 0, + value: BigNumber.from(5000000000), + transactionHex: + "010000000100000000000000000000000000000000000000000000000000000000" + + "00000000ffffffff0e04db07c34f0103062f503253482fffffffff0100f2052a01" + + "000000232102db6a0f2ef2e970eb1d2a84eabb5337f9cac0d85b49f209bffc4ec6" + + "805802e6a5ac00000000", } - ), - ] - - let tbtcContracts: MockTBTCContracts - let bitcoinClient: MockBitcoinClient - let bitcoinNetwork: BitcoinNetwork - let redemptionsService: TestRedemptionsService - - beforeEach(async () => { - tbtcContracts = new MockTBTCContracts() - bitcoinClient = new MockBitcoinClient() - redemptionsService = new TestRedemptionsService( - tbtcContracts, - bitcoinClient - ) - }) - - context("when wallet main UTXO is not set in the Bridge", () => { - beforeEach(async () => { - tbtcContracts.bridge.setWallet( - walletPublicKeyHash.toPrefixedString(), - { - mainUtxoHash: Hex.from( - "0x0000000000000000000000000000000000000000000000000000000000000000" - ), - } as Wallet - ) - }) - - it("should return undefined", async () => { - const mainUtxo = await redemptionsService.determineWalletMainUtxo( - walletPublicKeyHash, - bitcoinNetwork - ) - - expect(mainUtxo).to.be.undefined - }) - }) - - context("when wallet main UTXO is set in the Bridge", () => { - const tests = [ - { - testName: "recent witness transaction", - // Set the main UTXO hash based on the latest transaction from walletWitnessTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" - ), - outputIndex: 1, - value: BigNumber.from(200000), - }, - expectedMainUtxo: { - transactionHash: Hex.from( - "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" - ), - outputIndex: 1, - value: BigNumber.from(200000), - }, - }, - { - testName: "recent legacy transaction", - // Set the main UTXO hash based on the second last transaction from walletLegacyTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" - ), - outputIndex: 0, - value: BigNumber.from(100000), - }, - expectedMainUtxo: { - transactionHash: Hex.from( - "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" - ), - outputIndex: 0, - value: BigNumber.from(100000), - }, - }, - { - testName: "old witness transaction", - // Set the main UTXO hash based on the oldest transaction from walletWitnessTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1" - ), - outputIndex: 0, - value: BigNumber.from(100000), - }, - expectedMainUtxo: undefined, - }, - { - testName: "old legacy transaction", - // Set the main UTXO hash based on the oldest transaction from walletLegacyTransactionHistory. - actualMainUtxo: { - transactionHash: Hex.from( - "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267" - ), - outputIndex: 1, - value: BigNumber.from(200000), - }, - expectedMainUtxo: undefined, - }, - ] - - tests.forEach(({ testName, actualMainUtxo, expectedMainUtxo }) => { - context(`with main UTXO coming from ${testName}`, () => { - const networkTests = [ - { - networkTestName: "bitcoin testnet", - network: BitcoinNetwork.Testnet, - }, - { - networkTestName: "bitcoin mainnet", - network: BitcoinNetwork.Mainnet, - }, - ] - - networkTests.forEach(({ networkTestName, network }) => { - context(`with ${networkTestName} network`, () => { - beforeEach(async () => { - bitcoinNetwork = network - - const walletWitnessAddress = - BitcoinAddressConverter.publicKeyHashToAddress( - walletPublicKeyHash.toString(), - true, - bitcoinNetwork - ) - const walletLegacyAddress = - BitcoinAddressConverter.publicKeyHashToAddress( - walletPublicKeyHash.toString(), - false, - bitcoinNetwork - ) - - // Record the fake transaction history for both address types. - const transactionHistory = new Map() - transactionHistory.set( - walletWitnessAddress, - walletWitnessTransactionHistory - ) - transactionHistory.set( - walletLegacyAddress, - walletLegacyTransactionHistory - ) - bitcoinClient.transactionHistory = transactionHistory - - tbtcContracts.bridge.setWallet( - walletPublicKeyHash.toPrefixedString(), - { - mainUtxoHash: - tbtcContracts.bridge.buildUtxoHash(actualMainUtxo), - } as Wallet - ) - }) + const deposit = + depositSweepWithNoMainUtxoAndWitnessOutput.deposits[0].data - it("should return the expected main UTXO", async () => { - const mainUtxo = - await redemptionsService.determineWalletMainUtxo( - walletPublicKeyHash, - bitcoinNetwork - ) + it("should revert", async () => { + const walletTx = new WalletTx(tbtcContracts, bitcoinClient) - expect(mainUtxo).to.be.eql(expectedMainUtxo) - }) - }) - }) + await expect( + walletTx.depositSweep.assembleTransaction( + BitcoinNetwork.Testnet, + fee, + testnetWalletPrivateKey, + [utxoWithRaw], + [deposit] + ) + ).to.be.rejectedWith("Unsupported UTXO script type") }) }) }) }) - }) - describe("WalletTx", () => { describe("Redemption", () => { describe("submitTransaction", () => { let tbtcContracts: MockTBTCContracts @@ -2141,6 +2411,73 @@ describe("Redemption", () => { }) describe("Spv", () => { + describe("submitDepositSweepProof", () => { + let bitcoinClient: MockBitcoinClient + let tbtcContracts: MockTBTCContracts + let maintenanceService: MaintenanceService + + beforeEach(async () => { + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() + + maintenanceService = new MaintenanceService( + tbtcContracts, + bitcoinClient + ) + + const transactionHash = + depositSweepProof.bitcoinChainData.transaction.transactionHash + const transactions = new Map() + transactions.set( + transactionHash.toString(), + depositSweepProof.bitcoinChainData.transaction + ) + bitcoinClient.transactions = transactions + + const rawTransactions = new Map() + rawTransactions.set( + transactionHash.toString(), + depositSweepProof.bitcoinChainData.rawTransaction + ) + bitcoinClient.rawTransactions = rawTransactions + + bitcoinClient.latestHeight = + depositSweepProof.bitcoinChainData.latestBlockHeight + bitcoinClient.headersChain = + depositSweepProof.bitcoinChainData.headersChain + bitcoinClient.transactionMerkle = + depositSweepProof.bitcoinChainData.transactionMerkleBranch + const confirmations = new Map() + confirmations.set( + transactionHash.toString(), + depositSweepProof.bitcoinChainData.accumulatedTxConfirmations + ) + bitcoinClient.confirmations = confirmations + await maintenanceService.spv.submitDepositSweepProof( + transactionHash, + NO_MAIN_UTXO + ) + }) + + it("should submit deposit sweep proof with correct arguments", () => { + const bridgeLog = tbtcContracts.bridge.depositSweepProofLog + expect(bridgeLog.length).to.equal(1) + expect(bridgeLog[0].mainUtxo).to.equal(NO_MAIN_UTXO) + expect(bridgeLog[0].sweepTx).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepTx + ) + expect(bridgeLog[0].sweepProof.txIndexInBlock).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.txIndexInBlock + ) + expect(bridgeLog[0].sweepProof.merkleProof).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.merkleProof + ) + expect(bridgeLog[0].sweepProof.bitcoinHeaders).to.deep.equal( + depositSweepProof.expectedSweepProof.sweepProof.bitcoinHeaders + ) + }) + }) + describe("submitRedemptionProof", () => { const mainUtxo = { transactionHash: BitcoinTxHash.from( @@ -2223,40 +2560,3 @@ describe("Redemption", () => { }) }) }) - -async function runRedemptionScenario( - walletPrivKey: string, - bitcoinClient: MockBitcoinClient, - tbtcContracts: MockTBTCContracts, - data: RedemptionTestData -): Promise<{ - transactionHash: BitcoinTxHash - newMainUtxo?: BitcoinUtxo -}> { - const rawTransactions = new Map() - rawTransactions.set(data.mainUtxo.transactionHash.toString(), { - transactionHex: data.mainUtxo.transactionHex, - }) - bitcoinClient.rawTransactions = rawTransactions - - tbtcContracts.bridge.setPendingRedemptions( - new Map( - data.pendingRedemptions.map((redemption) => [ - redemption.redemptionKey, - redemption.pendingRedemption, - ]) - ) - ) - - const redeemerOutputScripts = data.pendingRedemptions.map( - (redemption) => redemption.pendingRedemption.redeemerOutputScript - ) - - const walletTx = new WalletTx(tbtcContracts, bitcoinClient, data.witness) - - return walletTx.redemption.submitTransaction( - walletPrivKey, - data.mainUtxo, - redeemerOutputScripts - ) -} diff --git a/typescript/test/services/redemptions.test.ts b/typescript/test/services/redemptions.test.ts new file mode 100644 index 000000000..1b9a82e24 --- /dev/null +++ b/typescript/test/services/redemptions.test.ts @@ -0,0 +1,839 @@ +import { + BitcoinAddressConverter, + BitcoinHashUtils, + BitcoinNetwork, + BitcoinRawTx, + BitcoinTx, + BitcoinTxHash, + BitcoinUtxo, + Hex, + NewWalletRegisteredEvent, + RedemptionRequest, + RedemptionsService, + Wallet, + WalletState, + WalletTx, +} from "../../src" +import { MockBitcoinClient } from "../utils/mock-bitcoin-client" +import { + findWalletForRedemptionData, + multipleRedemptionsWithWitnessChange, + RedemptionTestData, + singleP2PKHRedemptionWithWitnessChange, + walletPublicKey, +} from "../data/redemption" +import { MockBridge } from "../utils/mock-bridge" +import * as chai from "chai" +import { expect } from "chai" +import chaiAsPromised from "chai-as-promised" +import { BigNumber, BigNumberish } from "ethers" +import { MockTBTCContracts } from "../utils/mock-tbtc-contracts" + +chai.use(chaiAsPromised) + +describe("Redemptions", () => { + describe("RedemptionsService", () => { + describe("requestRedemption", () => { + const data: RedemptionTestData = singleP2PKHRedemptionWithWitnessChange + const { transactionHash, value } = data.mainUtxo + const mainUtxo: BitcoinUtxo = { + transactionHash, + outputIndex: 0, + value, + } + const redeemerOutputScript = + data.pendingRedemptions[0].pendingRedemption.redeemerOutputScript + const amount = + data.pendingRedemptions[0].pendingRedemption.requestedAmount + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + + const walletPublicKeyHash = Hex.from( + BitcoinHashUtils.computeHash160(walletPublicKey) + ) + + // Prepare NewWalletRegisteredEvent history. Set only relevant fields. + tbtcContracts.bridge.newWalletRegisteredEvents = [ + { + walletPublicKeyHash: walletPublicKeyHash, + } as NewWalletRegisteredEvent, + ] + + // Prepare wallet data in the Bridge. Set only relevant fields. + tbtcContracts.bridge.setWallet(walletPublicKeyHash.toPrefixedString(), { + state: WalletState.Live, + walletPublicKey: Hex.from(walletPublicKey), + pendingRedemptionsValue: BigNumber.from(0), + mainUtxoHash: tbtcContracts.bridge.buildUtxoHash(mainUtxo), + } as Wallet) + + const walletAddress = BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + true, + BitcoinNetwork.Testnet + ) + + // Prepare wallet transaction history for main UTXO lookup. + // Set only relevant fields. + const transactionHistory = new Map() + transactionHistory.set(walletAddress, [ + { + transactionHash: mainUtxo.transactionHash, + outputs: [ + { + outputIndex: mainUtxo.outputIndex, + value: mainUtxo.value, + scriptPubKey: BitcoinAddressConverter.addressToOutputScript( + walletAddress, + BitcoinNetwork.Testnet + ), + }, + ], + } as BitcoinTx, + ]) + bitcoinClient.transactionHistory = transactionHistory + + const redemptionsService = new RedemptionsService( + tbtcContracts, + bitcoinClient + ) + + await redemptionsService.requestRedemption( + BitcoinAddressConverter.outputScriptToAddress( + Hex.from(redeemerOutputScript), + BitcoinNetwork.Testnet + ), + amount + ) + }) + + it("should submit redemption request with correct arguments", () => { + const tokenLog = tbtcContracts.tbtcToken.requestRedemptionLog + + expect(tokenLog.length).to.equal(1) + expect(tokenLog[0]).to.deep.equal({ + walletPublicKey, + mainUtxo, + redeemerOutputScript, + amount, + }) + }) + }) + + describe("getRedemptionRequest", () => { + context("when asked for a pending request", () => { + const { redemptionKey, pendingRedemption: redemptionRequest } = + multipleRedemptionsWithWitnessChange.pendingRedemptions[0] + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let redemptionsService: RedemptionsService + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + + const pendingRedemptions = new Map() + pendingRedemptions.set(redemptionKey, redemptionRequest) + + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + + redemptionsService = new RedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + it("should return the expected redemption request", async () => { + const actualRedemptionRequest = + await redemptionsService.getRedemptionRequests( + BitcoinAddressConverter.outputScriptToAddress( + Hex.from(redemptionRequest.redeemerOutputScript), + BitcoinNetwork.Testnet + ), + walletPublicKey, + "pending" + ) + + expect(actualRedemptionRequest).to.be.eql(redemptionRequest) + }) + }) + + context("when asked for a timed-out request", () => { + const { redemptionKey, pendingRedemption: redemptionRequest } = + multipleRedemptionsWithWitnessChange.pendingRedemptions[0] + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let redemptionsService: RedemptionsService + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + + const timedOutRedemptions = new Map() + timedOutRedemptions.set(redemptionKey, redemptionRequest) + + tbtcContracts.bridge.setTimedOutRedemptions(timedOutRedemptions) + + redemptionsService = new RedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + it("should return the expected redemption request", async () => { + const actualRedemptionRequest = + await redemptionsService.getRedemptionRequests( + BitcoinAddressConverter.outputScriptToAddress( + Hex.from(redemptionRequest.redeemerOutputScript), + BitcoinNetwork.Testnet + ), + walletPublicKey, + "timedOut" + ) + + expect(actualRedemptionRequest).to.be.eql(redemptionRequest) + }) + }) + }) + + describe("findWalletForRedemption", () => { + class TestRedemptionsService extends RedemptionsService { + public async findWalletForRedemption( + redeemerOutputScript: string, + amount: BigNumber + ): Promise<{ + walletPublicKey: string + mainUtxo: BitcoinUtxo + }> { + return super.findWalletForRedemption(redeemerOutputScript, amount) + } + } + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let redemptionsService: TestRedemptionsService + // script for testnet P2WSH address + // tb1qau95mxzh2249aa3y8exx76ltc2sq0e7kw8hj04936rdcmnynhswqqz02vv + const redeemerOutputScript = + "0x220020ef0b4d985752aa5ef6243e4c6f6bebc2a007e7d671ef27d4b1d0db8dcc93bc1c" + + context( + "when there are no wallets in the network that can handle redemption", + () => { + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + beforeEach(() => { + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() + tbtcContracts.bridge.newWalletRegisteredEvents = [] + redemptionsService = new TestRedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + it("should throw an error", async () => { + await expect( + redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + ).to.be.rejectedWith( + "Currently, there are no live wallets in the network." + ) + }) + } + ) + + context("when there are registered wallets in the network", () => { + let result: Awaited | never> + const walletsOrder = [ + findWalletForRedemptionData.nonLiveWallet, + findWalletForRedemptionData.walletWithoutUtxo, + findWalletForRedemptionData.walletWithPendingRedemption, + findWalletForRedemptionData.liveWallet, + ] + + beforeEach(async () => { + bitcoinClient = new MockBitcoinClient() + tbtcContracts = new MockTBTCContracts() + + tbtcContracts.bridge.newWalletRegisteredEvents = walletsOrder.map( + (wallet) => wallet.event + ) + + const walletsTransactionHistory = new Map() + + walletsOrder.forEach((wallet) => { + const { + state, + mainUtxoHash, + walletPublicKey, + btcAddress, + transactions, + pendingRedemptionsValue, + } = wallet.data + + walletsTransactionHistory.set(btcAddress, transactions) + tbtcContracts.bridge.setWallet( + wallet.event.walletPublicKeyHash.toPrefixedString(), + { + state, + mainUtxoHash, + walletPublicKey, + pendingRedemptionsValue, + } as Wallet + ) + }) + + bitcoinClient.transactionHistory = walletsTransactionHistory + + redemptionsService = new TestRedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + context( + "when there is a wallet that can handle the redemption request", + () => { + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + beforeEach(async () => { + result = await redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + }) + + it("should get all registered wallets", () => { + const bridgeQueryEventsLog = + tbtcContracts.bridge.newWalletRegisteredEventsLog + + expect(bridgeQueryEventsLog.length).to.equal(1) + expect(bridgeQueryEventsLog[0]).to.deep.equal({ + options: undefined, + filterArgs: [], + }) + }) + + it("should return the wallet data that can handle redemption request", () => { + const expectedWalletData = + findWalletForRedemptionData.walletWithPendingRedemption.data + + expect(result).to.deep.eq({ + walletPublicKey: expectedWalletData.walletPublicKey.toString(), + mainUtxo: expectedWalletData.mainUtxo, + }) + }) + } + ) + + context( + "when the redemption request amount is too large and no wallet can handle the request", + () => { + const amount = BigNumber.from("10000000000") // 1 000 BTC + const expectedMaxAmount = walletsOrder + .map((wallet) => wallet.data) + .map((wallet) => wallet.mainUtxo) + .map((utxo) => utxo.value) + .sort((a, b) => (b.gt(a) ? 0 : -1))[0] + + it("should throw an error", async () => { + await expect( + redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + ).to.be.rejectedWith( + `Could not find a wallet with enough funds. Maximum redemption amount is ${expectedMaxAmount.toString()} Satoshi.` + ) + }) + } + ) + + context( + "when there is pending redemption request from a given wallet to the same address", + () => { + beforeEach(async () => { + const redeemerOutputScript = + findWalletForRedemptionData.pendingRedemption + .redeemerOutputScript + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + + const walletPublicKeyHash = + findWalletForRedemptionData.walletWithPendingRedemption.event + .walletPublicKeyHash + + const pendingRedemptions = new Map< + BigNumberish, + RedemptionRequest + >() + + const key = MockBridge.buildRedemptionKey( + walletPublicKeyHash.toString(), + redeemerOutputScript + ) + + pendingRedemptions.set( + key, + findWalletForRedemptionData.pendingRedemption + ) + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + + result = await redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + }) + + it("should get all registered wallets", () => { + const bridgeQueryEventsLog = + tbtcContracts.bridge.newWalletRegisteredEventsLog + + expect(bridgeQueryEventsLog.length).to.equal(1) + expect(bridgeQueryEventsLog[0]).to.deep.equal({ + options: undefined, + filterArgs: [], + }) + }) + + it("should skip the wallet for which there is a pending redemption request to the same redeemer output script and return the wallet data that can handle redemption request", () => { + const expectedWalletData = + findWalletForRedemptionData.liveWallet.data + + expect(result).to.deep.eq({ + walletPublicKey: expectedWalletData.walletPublicKey.toString(), + mainUtxo: expectedWalletData.mainUtxo, + }) + }) + } + ) + + context( + "when wallet has pending redemptions and the requested amount is greater than possible", + () => { + beforeEach(async () => { + const wallet = + findWalletForRedemptionData.walletWithPendingRedemption + const walletBTCBalance = wallet.data.mainUtxo.value + + const amount: BigNumber = walletBTCBalance + .sub(wallet.data.pendingRedemptionsValue) + .add(BigNumber.from(500000)) // 0.005 BTC + + console.log("amount", amount.toString()) + + result = await redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + }) + + it("should skip the wallet wallet with pending redemptions and return the wallet data that can handle redemption request ", () => { + const expectedWalletData = + findWalletForRedemptionData.liveWallet.data + + expect(result).to.deep.eq({ + walletPublicKey: expectedWalletData.walletPublicKey.toString(), + mainUtxo: expectedWalletData.mainUtxo, + }) + }) + } + ) + + context( + "when all active wallets has pending redemption for a given Bitcoin address", + () => { + const amount: BigNumber = BigNumber.from("1000000") // 0.01 BTC + const redeemerOutputScript = + findWalletForRedemptionData.pendingRedemption.redeemerOutputScript + + beforeEach(async () => { + const walletPublicKeyHash = + findWalletForRedemptionData.walletWithPendingRedemption.event + .walletPublicKeyHash + + const pendingRedemptions = new Map< + BigNumberish, + RedemptionRequest + >() + + const pendingRedemption1 = MockBridge.buildRedemptionKey( + walletPublicKeyHash.toString(), + redeemerOutputScript + ) + + const pendingRedemption2 = MockBridge.buildRedemptionKey( + findWalletForRedemptionData.liveWallet.event.walletPublicKeyHash.toString(), + redeemerOutputScript + ) + + pendingRedemptions.set( + pendingRedemption1, + findWalletForRedemptionData.pendingRedemption + ) + + pendingRedemptions.set( + pendingRedemption2, + findWalletForRedemptionData.pendingRedemption + ) + tbtcContracts.bridge.setPendingRedemptions(pendingRedemptions) + }) + + it("should throw an error", async () => { + await expect( + redemptionsService.findWalletForRedemption( + redeemerOutputScript, + amount + ) + ).to.be.rejectedWith( + "All live wallets in the network have the pending redemption for a given Bitcoin address. Please use another Bitcoin address." + ) + }) + } + ) + }) + }) + + describe("determineWalletMainUtxo", () => { + class TestRedemptionsService extends RedemptionsService { + public async determineWalletMainUtxo( + walletPublicKeyHash: Hex, + bitcoinNetwork: BitcoinNetwork + ): Promise { + return super.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + } + } + + // Just an arbitrary 20-byte wallet public key hash. + const walletPublicKeyHash = Hex.from( + "e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0" + ) + + // Helper function facilitating creation of mock transactions. + const mockTransaction = ( + hash: string, + outputs: Record // key: locking script, value: amount of locked satoshis + ): BitcoinTx => { + return { + transactionHash: Hex.from(hash), + inputs: [], // not relevant in this test scenario + outputs: Object.entries(outputs).map( + ([scriptPubKey, value], index) => ({ + outputIndex: index, + value: BigNumber.from(value), + scriptPubKey: Hex.from(scriptPubKey), + }) + ), + } + } + + // Create a fake wallet witness transaction history that consists of 6 transactions. + const walletWitnessTransactionHistory: BitcoinTx[] = [ + mockTransaction( + "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1", + { + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "4c6b33b7c0550e0e536a5d119ac7189d71e1296fcb0c258e0c115356895bc0e6", + { + "00140000000000000000000000000000000000000001": 100000, + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output + } + ), + mockTransaction( + "44863a79ce2b8fec9792403d5048506e50ffa7338191db0e6c30d3d3358ea2f6", + { + "00140000000000000000000000000000000000000001": 100000, + "00140000000000000000000000000000000000000002": 200000, + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 300000, // wallet witness output + } + ), + mockTransaction( + "f65bc5029251f0042aedb37f90dbb2bfb63a2e81694beef9cae5ec62e954c22e", + { + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "2724545276df61f43f1e92c4b9f1dd3c9109595c022dbd9dc003efbad8ded38b", + { + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 100000, // wallet witness output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214", + { + "00140000000000000000000000000000000000000001": 100000, + "0014e6f9d74726b19b75f16fe1e9feaec048aa4fa1d0": 200000, // wallet witness output + } + ), + ] + + // Create a fake wallet legacy transaction history that consists of 6 transactions. + const walletLegacyTransactionHistory: BitcoinTx[] = [ + mockTransaction( + "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267", + { + "00140000000000000000000000000000000000000001": 100000, + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output + } + ), + mockTransaction( + "b11bfc481b95769b8488bd661d5f61a35f7c3c757160494d63f6e04e532dfcb9", + { + "00140000000000000000000000000000000000000001": 100000, + "00140000000000000000000000000000000000000002": 200000, + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 300000, // wallet legacy output + } + ), + mockTransaction( + "7e91580d989f8541489a37431381ff9babd596111232f1bc7a1a1ba503c27dee", + { + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "5404e339ba82e6e52fcc24cb40029bed8425baa4c7f869626ef9de956186f910", + { + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94", + { + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 100000, // wallet legacy output + "00140000000000000000000000000000000000000001": 200000, + } + ), + mockTransaction( + "00cc0cd13fc4de7a15cb41ab6d58f8b31c75b6b9b4194958c381441a67d09b08", + { + "00140000000000000000000000000000000000000001": 100000, + "76a914e6f9d74726b19b75f16fe1e9feaec048aa4fa1d088ac": 200000, // wallet legacy output + } + ), + ] + + let tbtcContracts: MockTBTCContracts + let bitcoinClient: MockBitcoinClient + let bitcoinNetwork: BitcoinNetwork + let redemptionsService: TestRedemptionsService + + beforeEach(async () => { + tbtcContracts = new MockTBTCContracts() + bitcoinClient = new MockBitcoinClient() + redemptionsService = new TestRedemptionsService( + tbtcContracts, + bitcoinClient + ) + }) + + context("when wallet main UTXO is not set in the Bridge", () => { + beforeEach(async () => { + tbtcContracts.bridge.setWallet( + walletPublicKeyHash.toPrefixedString(), + { + mainUtxoHash: Hex.from( + "0x0000000000000000000000000000000000000000000000000000000000000000" + ), + } as Wallet + ) + }) + + it("should return undefined", async () => { + const mainUtxo = await redemptionsService.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + + expect(mainUtxo).to.be.undefined + }) + }) + + context("when wallet main UTXO is set in the Bridge", () => { + const tests = [ + { + testName: "recent witness transaction", + // Set the main UTXO hash based on the latest transaction from walletWitnessTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" + ), + outputIndex: 1, + value: BigNumber.from(200000), + }, + expectedMainUtxo: { + transactionHash: Hex.from( + "ea374ab6842723c647c3fc0ab281ca0641eaa768576cf9df695ca5b827140214" + ), + outputIndex: 1, + value: BigNumber.from(200000), + }, + }, + { + testName: "recent legacy transaction", + // Set the main UTXO hash based on the second last transaction from walletLegacyTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" + ), + outputIndex: 0, + value: BigNumber.from(100000), + }, + expectedMainUtxo: { + transactionHash: Hex.from( + "05dabb0291c0a6aa522de5ded5cb6d14ee2159e7ff109d3ef0f21de128b56b94" + ), + outputIndex: 0, + value: BigNumber.from(100000), + }, + }, + { + testName: "old witness transaction", + // Set the main UTXO hash based on the oldest transaction from walletWitnessTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "3ca4ae3f8ee3b48949192bc7a146c8d9862267816258c85e02a44678364551e1" + ), + outputIndex: 0, + value: BigNumber.from(100000), + }, + expectedMainUtxo: undefined, + }, + { + testName: "old legacy transaction", + // Set the main UTXO hash based on the oldest transaction from walletLegacyTransactionHistory. + actualMainUtxo: { + transactionHash: Hex.from( + "230a19d8867ff3f5b409e924d9dd6413188e215f9bb52f1c47de6154dac42267" + ), + outputIndex: 1, + value: BigNumber.from(200000), + }, + expectedMainUtxo: undefined, + }, + ] + + tests.forEach(({ testName, actualMainUtxo, expectedMainUtxo }) => { + context(`with main UTXO coming from ${testName}`, () => { + const networkTests = [ + { + networkTestName: "bitcoin testnet", + network: BitcoinNetwork.Testnet, + }, + { + networkTestName: "bitcoin mainnet", + network: BitcoinNetwork.Mainnet, + }, + ] + + networkTests.forEach(({ networkTestName, network }) => { + context(`with ${networkTestName} network`, () => { + beforeEach(async () => { + bitcoinNetwork = network + + const walletWitnessAddress = + BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + true, + bitcoinNetwork + ) + const walletLegacyAddress = + BitcoinAddressConverter.publicKeyHashToAddress( + walletPublicKeyHash.toString(), + false, + bitcoinNetwork + ) + + // Record the fake transaction history for both address types. + const transactionHistory = new Map() + transactionHistory.set( + walletWitnessAddress, + walletWitnessTransactionHistory + ) + transactionHistory.set( + walletLegacyAddress, + walletLegacyTransactionHistory + ) + bitcoinClient.transactionHistory = transactionHistory + + tbtcContracts.bridge.setWallet( + walletPublicKeyHash.toPrefixedString(), + { + mainUtxoHash: + tbtcContracts.bridge.buildUtxoHash(actualMainUtxo), + } as Wallet + ) + }) + + it("should return the expected main UTXO", async () => { + const mainUtxo = + await redemptionsService.determineWalletMainUtxo( + walletPublicKeyHash, + bitcoinNetwork + ) + + expect(mainUtxo).to.be.eql(expectedMainUtxo) + }) + }) + }) + }) + }) + }) + }) + }) +}) + +export async function runRedemptionScenario( + walletPrivKey: string, + bitcoinClient: MockBitcoinClient, + tbtcContracts: MockTBTCContracts, + data: RedemptionTestData +): Promise<{ + transactionHash: BitcoinTxHash + newMainUtxo?: BitcoinUtxo +}> { + const rawTransactions = new Map() + rawTransactions.set(data.mainUtxo.transactionHash.toString(), { + transactionHex: data.mainUtxo.transactionHex, + }) + bitcoinClient.rawTransactions = rawTransactions + + tbtcContracts.bridge.setPendingRedemptions( + new Map( + data.pendingRedemptions.map((redemption) => [ + redemption.redemptionKey, + redemption.pendingRedemption, + ]) + ) + ) + + const redeemerOutputScripts = data.pendingRedemptions.map( + (redemption) => redemption.pendingRedemption.redeemerOutputScript + ) + + const walletTx = new WalletTx(tbtcContracts, bitcoinClient, data.witness) + + return walletTx.redemption.submitTransaction( + walletPrivKey, + data.mainUtxo, + redeemerOutputScripts + ) +} From 3d0e38b6ef4e3b79db89a10d875c315abc91b82a Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Wed, 11 Oct 2023 11:26:02 +0200 Subject: [PATCH 35/43] Adjust refund.ts script --- typescript/scripts/refund.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/typescript/scripts/refund.ts b/typescript/scripts/refund.ts index 1eb56ab36..11931b978 100644 --- a/typescript/scripts/refund.ts +++ b/typescript/scripts/refund.ts @@ -1,10 +1,15 @@ import { BigNumber } from "ethers" -import { Deposit } from "../src/deposit" -import { submitDepositRefundTransaction } from "../src/deposit-refund" -import { TransactionHash, UnspentTransactionOutput } from "../src/bitcoin" -import { Client as ElectrumClient } from "../src/electrum" import { program } from "commander" -import fs from "fs" +import * as fs from "fs" +import { + BitcoinTxHash, + BitcoinUtxo, + DepositReceipt, + DepositRefund, + DepositScript, + ElectrumClient, + ElectrumCredentials, +} from "../src" program .version("0.0.1") @@ -49,13 +54,12 @@ const electrumCredentials = { host: options.host, port: options.port, protocol: options.protocol, -} +} as ElectrumCredentials const depositJson = JSON.parse(fs.readFileSync(depositJsonPath, "utf-8")) -const deposit: Deposit = { +const deposit: DepositReceipt = { depositor: depositJson.depositor, - amount: BigNumber.from(refundAmount), walletPublicKeyHash: depositJson.walletPublicKeyHash, refundPublicKeyHash: depositJson.refundPublicKeyHash, blindingFactor: depositJson.blindingFactor, @@ -73,19 +77,21 @@ console.log("electrum credentials:", electrumCredentials) console.log("=====================================") async function run(): Promise { - const client = new ElectrumClient(electrumCredentials) + const client = new ElectrumClient([electrumCredentials]) - const depositUtxo: UnspentTransactionOutput = { - transactionHash: TransactionHash.from(transactionId), + const depositUtxo: BitcoinUtxo = { + transactionHash: BitcoinTxHash.from(transactionId), outputIndex: Number(transactionIndex), value: BigNumber.from(refundAmount), } - const refundTxHash = await submitDepositRefundTransaction( + const depositScript = DepositScript.fromReceipt(deposit) + const depositRefund = DepositRefund.fromScript(depositScript) + + const refundTxHash = await depositRefund.submitTransaction( client, BigNumber.from(fee), depositUtxo, - deposit, recoveryAddress, refunderPrivateKey ) From 4d4f45cf2da4f9cde2e349cd62f5298c7cbedbff Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 14:50:43 +0200 Subject: [PATCH 36/43] Make `BitcoinCompactSizeUint.read` throw on empty varlen data --- typescript/src/lib/bitcoin/csuint.ts | 6 +++++- typescript/test/lib/bitcoin.test.ts | 14 +++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/typescript/src/lib/bitcoin/csuint.ts b/typescript/src/lib/bitcoin/csuint.ts index e0576e176..163bc30c1 100644 --- a/typescript/src/lib/bitcoin/csuint.ts +++ b/typescript/src/lib/bitcoin/csuint.ts @@ -14,6 +14,10 @@ function read(varLenData: Hex): { value: number byteLength: number } { + if (varLenData.toString().length == 0) { + throw new Error("Empty variable length data argument") + } + // The varLenData is prefixed with the compact size uint. According to the docs // (https://developer.bitcoin.org/reference/transactions.html#compactsize-unsigned-integers) // a compact size uint can be 1, 3, 5 or 9 bytes. To determine the exact length, @@ -26,7 +30,7 @@ function read(varLenData: Hex): { case "fe": case "fd": { throw new Error( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + "Support for 3, 5 and 9 bytes compact size uints is not implemented yet" ) } default: { diff --git a/typescript/test/lib/bitcoin.test.ts b/typescript/test/lib/bitcoin.test.ts index 8483732ba..8fdd766a1 100644 --- a/typescript/test/lib/bitcoin.test.ts +++ b/typescript/test/lib/bitcoin.test.ts @@ -765,6 +765,14 @@ describe("Bitcoin", () => { const { read } = BitcoinCompactSizeUint describe("read", () => { + context("when the compact size uint is empty", () => { + it("should throw", () => { + expect(() => { + return read(Hex.from("")) + }).to.throw("Empty variable length data argument") + }) + }) + context("when the compact size uint is 1-byte", () => { it("should return the the uint value and byte length", () => { expect(read(Hex.from("bb"))).to.be.eql({ @@ -777,7 +785,7 @@ describe("Bitcoin", () => { context("when the compact size uint is 3-byte", () => { it("should throw", () => { expect(() => read(Hex.from("fd0302"))).to.throw( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + "Support for 3, 5 and 9 bytes compact size uints is not implemented yet" ) }) }) @@ -785,7 +793,7 @@ describe("Bitcoin", () => { context("when the compact size uint is 5-byte", () => { it("should throw", () => { expect(() => read(Hex.from("fe703a0f00"))).to.throw( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + "Support for 3, 5 and 9 bytes compact size uints is not implemented yet" ) }) }) @@ -795,7 +803,7 @@ describe("Bitcoin", () => { expect(() => { return read(Hex.from("ff57284e56dab40000")) }).to.throw( - "support for 3, 5 and 9 bytes compact size uints is not implemented yet" + "Support for 3, 5 and 9 bytes compact size uints is not implemented yet" ) }) }) From 2343c83854a38e7c12a3e627935e8ef718600d07 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 14:52:38 +0200 Subject: [PATCH 37/43] Fix some typos around SDK functions --- typescript/src/lib/bitcoin/csuint.ts | 2 +- typescript/src/lib/bitcoin/ecdsa-key.ts | 4 ++-- typescript/test/lib/bitcoin.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/typescript/src/lib/bitcoin/csuint.ts b/typescript/src/lib/bitcoin/csuint.ts index 163bc30c1..e32dc8932 100644 --- a/typescript/src/lib/bitcoin/csuint.ts +++ b/typescript/src/lib/bitcoin/csuint.ts @@ -35,7 +35,7 @@ function read(varLenData: Hex): { } default: { // The discriminant tells the compact size uint is 1 byte. That means - // the discriminant represent the value itself. + // the discriminant represents the value itself. return { value: parseInt(discriminant, 16), byteLength: 1, diff --git a/typescript/src/lib/bitcoin/ecdsa-key.ts b/typescript/src/lib/bitcoin/ecdsa-key.ts index f7b845840..4c7b38b77 100644 --- a/typescript/src/lib/bitcoin/ecdsa-key.ts +++ b/typescript/src/lib/bitcoin/ecdsa-key.ts @@ -52,7 +52,7 @@ function compressPublicKey(publicKey: string | Hex): string { } /** - * Utility functions allowing to perform Bitcoin ECDSA public keys. + * Utility functions allowing to perform operations on Bitcoin ECDSA public keys. */ export const BitcoinPublicKeyUtils = { isCompressedPublicKey, @@ -77,7 +77,7 @@ function createKeyPair( } /** - * Utility functions allowing to perform Bitcoin ECDSA public keys. + * Utility functions allowing to perform operations on Bitcoin ECDSA private keys. */ export const BitcoinPrivateKeyUtils = { createKeyPair, diff --git a/typescript/test/lib/bitcoin.test.ts b/typescript/test/lib/bitcoin.test.ts index 8fdd766a1..80e1212c3 100644 --- a/typescript/test/lib/bitcoin.test.ts +++ b/typescript/test/lib/bitcoin.test.ts @@ -774,7 +774,7 @@ describe("Bitcoin", () => { }) context("when the compact size uint is 1-byte", () => { - it("should return the the uint value and byte length", () => { + it("should return the uint value and byte length", () => { expect(read(Hex.from("bb"))).to.be.eql({ value: 187, byteLength: 1, From c359fef9218dfc2913c60e6466176011608fe104 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 14:55:40 +0200 Subject: [PATCH 38/43] Use ternary operator in `BitcoinPublicKeyUtils.compressPublicKey` function --- typescript/src/lib/bitcoin/ecdsa-key.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/typescript/src/lib/bitcoin/ecdsa-key.ts b/typescript/src/lib/bitcoin/ecdsa-key.ts index 4c7b38b77..214e59bf4 100644 --- a/typescript/src/lib/bitcoin/ecdsa-key.ts +++ b/typescript/src/lib/bitcoin/ecdsa-key.ts @@ -41,12 +41,7 @@ function compressPublicKey(publicKey: string | Hex): string { // The Y coordinate is the next 32 bytes. const publicKeyY = publicKey.substring(64) - let prefix: string - if (BigNumber.from(`0x${publicKeyY}`).mod(2).eq(0)) { - prefix = "02" - } else { - prefix = "03" - } + const prefix = BigNumber.from(`0x${publicKeyY}`).mod(2).eq(0) ? "02" : "03" return `${prefix}${publicKeyX}` } From ad51b2ed85092a42e99b1e4e4276038d2f376555 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 15:03:33 +0200 Subject: [PATCH 39/43] Improvements around SDK functions docstrings --- typescript/src/lib/bitcoin/address.ts | 6 +++--- typescript/src/lib/bitcoin/ecdsa-key.ts | 1 + typescript/src/lib/bitcoin/hash.ts | 10 +++++----- typescript/src/lib/bitcoin/tx.ts | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts index 3d5dd2594..08231f769 100644 --- a/typescript/src/lib/bitcoin/address.ts +++ b/typescript/src/lib/bitcoin/address.ts @@ -5,9 +5,9 @@ import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./network" /** * Creates the Bitcoin address from the public key. Supports SegWit (P2WPKH) and * Legacy (P2PKH) formats. - * @param publicKey - Public key used to derive the Bitcoin address. - * @param bitcoinNetwork - Target Bitcoin network. - * @param witness - Flag to determine address format: true for SegWit (P2WPKH) + * @param publicKey Compressed public key used to derive the Bitcoin address. + * @param bitcoinNetwork Target Bitcoin network. + * @param witness Flag to determine address format: true for SegWit (P2WPKH) * and false for Legacy (P2PKH). Default is true. * @returns The derived Bitcoin address. */ diff --git a/typescript/src/lib/bitcoin/ecdsa-key.ts b/typescript/src/lib/bitcoin/ecdsa-key.ts index 214e59bf4..f622490be 100644 --- a/typescript/src/lib/bitcoin/ecdsa-key.ts +++ b/typescript/src/lib/bitcoin/ecdsa-key.ts @@ -57,6 +57,7 @@ export const BitcoinPublicKeyUtils = { /** * Creates a Bitcoin key pair based on the given private key. * @param privateKey Private key that should be used to create the key pair. + * Should be passed in the WIF format. * @param bitcoinNetwork Bitcoin network the given key pair is relevant for. * @returns Bitcoin key ring. */ diff --git a/typescript/src/lib/bitcoin/hash.ts b/typescript/src/lib/bitcoin/hash.ts index 018771f19..876e69888 100644 --- a/typescript/src/lib/bitcoin/hash.ts +++ b/typescript/src/lib/bitcoin/hash.ts @@ -2,8 +2,8 @@ import { BigNumber, utils } from "ethers" import { Hex } from "../utils" /** - * Computes the HASH160 for the given text. - * @param text - Text the HASH160 is computed for. + * Computes the HASH160 (i.e. RIPEMD160(SHA256(text))) for the given text. + * @param text Text the HASH160 is computed for. * @returns Hash as a 20-byte un-prefixed hex string. */ function computeHash160(text: string): string { @@ -17,7 +17,7 @@ function computeHash160(text: string): string { /** * Computes the double SHA256 for the given text. - * @param text - Text the double SHA256 is computed for. + * @param text Text the double SHA256 is computed for. * @returns Hash as a 32-byte un-prefixed hex string. * @dev Do not confuse it with computeSha256 which computes single SHA256. */ @@ -30,7 +30,7 @@ function computeHash256(text: Hex): Hex { /** * Converts a hash in hex string in little endian to a BigNumber. - * @param hash - Hash in hex-string format. + * @param hash Hash in hex-string format. * @returns BigNumber representation of the hash. */ function hashLEToBigNumber(hash: Hex): BigNumber { @@ -39,7 +39,7 @@ function hashLEToBigNumber(hash: Hex): BigNumber { /** * Computes the single SHA256 for the given text. - * @param text - Text the single SHA256 is computed for. + * @param text Text the single SHA256 is computed for. * @returns Hash as a 32-byte un-prefixed hex string. * @dev Do not confuse it with computeHash256 which computes double SHA256. */ diff --git a/typescript/src/lib/bitcoin/tx.ts b/typescript/src/lib/bitcoin/tx.ts index f068500b1..3593ddfe6 100644 --- a/typescript/src/lib/bitcoin/tx.ts +++ b/typescript/src/lib/bitcoin/tx.ts @@ -185,7 +185,7 @@ export function extractBitcoinRawTxVectors( /** * Converts Bitcoin specific locktime value to a number. The number represents - * either a block height or an Unix timestamp depending on the value. + * either a block height or a Unix timestamp, depending on the value. * * If the number is less than 500 000 000 it is a block height. * If the number is greater or equal 500 000 000 it is a Unix timestamp. From cb80637751541aa710bc2bf69fcce316453a0c44 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 15:07:20 +0200 Subject: [PATCH 40/43] Include `src` and `typechain` in the resulting npm package This is needed to make the source maps work correctly. --- typescript/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/package.json b/typescript/package.json index c241f46bb..b7cc037e9 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -16,7 +16,9 @@ "dev": "tsc --project tsconfig.build.json --watch" }, "files": [ - "dist" + "dist/", + "src/", + "typechain/" ], "config": { "contracts": "./node_modules/@keep-network/ecdsa/artifacts/WalletRegistry.json ./node_modules/@keep-network/tbtc-v2/artifacts/{Bridge,TBTCVault,TBTC}.json" From e063022e2840cfaf71c672dd5c4bc8c108622404 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 15:09:26 +0200 Subject: [PATCH 41/43] Do not export `publicKeyToAddress` directly --- typescript/src/lib/bitcoin/address.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/src/lib/bitcoin/address.ts b/typescript/src/lib/bitcoin/address.ts index 08231f769..36f660ba2 100644 --- a/typescript/src/lib/bitcoin/address.ts +++ b/typescript/src/lib/bitcoin/address.ts @@ -11,7 +11,7 @@ import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./network" * and false for Legacy (P2PKH). Default is true. * @returns The derived Bitcoin address. */ -export function publicKeyToAddress( +function publicKeyToAddress( publicKey: Hex, bitcoinNetwork: BitcoinNetwork, witness: boolean = true From 62ac6082ec3cecb85ce0c3724c6241a20bdafa36 Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 15:13:45 +0200 Subject: [PATCH 42/43] Add header index to error message thrown by `validateBitcoinHeadersChain` --- typescript/src/lib/bitcoin/header.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/typescript/src/lib/bitcoin/header.ts b/typescript/src/lib/bitcoin/header.ts index 06531d933..99faa237a 100644 --- a/typescript/src/lib/bitcoin/header.ts +++ b/typescript/src/lib/bitcoin/header.ts @@ -147,7 +147,9 @@ export function validateBitcoinHeadersChain( if ( !previousBlockHeaderHash.equals(currentHeader.previousBlockHeaderHash) ) { - throw new Error("Invalid headers chain") + throw new Error( + `Invalid headers chain; problem with header index: ${index}` + ) } } From 4bdc2a14dc49330610fb1a412e1cf8823f9044ca Mon Sep 17 00:00:00 2001 From: Lukasz Zimnoch Date: Mon, 16 Oct 2023 15:15:47 +0200 Subject: [PATCH 43/43] Update supported Node.js and Yarn versions in READMEs --- typescript/README.adoc | 4 ++-- typescript/scripts/README.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/typescript/README.adoc b/typescript/README.adoc index 36fadc0c0..9d518ab76 100644 --- a/typescript/README.adoc +++ b/typescript/README.adoc @@ -14,8 +14,8 @@ toc::[] Please make sure you have the following prerequisites installed on your machine: -- https://nodejs.org[Node.js] >14.17.4 -- https://yarnpkg.com[Yarn] >1.22.10 +- https://nodejs.org[Node.js] >=16 +- https://yarnpkg.com[Yarn] >=1.22.19 === Install dependencies diff --git a/typescript/scripts/README.adoc b/typescript/scripts/README.adoc index b057d463a..da1ad4bdb 100644 --- a/typescript/scripts/README.adoc +++ b/typescript/scripts/README.adoc @@ -12,8 +12,8 @@ toc::[] Please make sure you have the following prerequisites installed on your machine: -- https://nodejs.org[Node.js] >14.18.0 -- https://yarnpkg.com[Yarn] >1.22.19 +- https://nodejs.org[Node.js] >=16 +- https://yarnpkg.com[Yarn] >=1.22.19 tBTCv2 system prerequisite is that you can refund your BTC only if it was not used for minting tBTC and after `refundLocktime` has passed.