From 26e75b913516f6a24a9465bbfce84f3ee8bf6efb Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 13:02:48 +0200 Subject: [PATCH 01/51] Implement HttpApi class for GET and POST requests The abstract HttpApi class will be used for integration with external APIs to fetch GET and POST requests. --- sdk/src/api/HttpApi.ts | 47 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 sdk/src/api/HttpApi.ts diff --git a/sdk/src/api/HttpApi.ts b/sdk/src/api/HttpApi.ts new file mode 100644 index 000000000..351cd8f6e --- /dev/null +++ b/sdk/src/api/HttpApi.ts @@ -0,0 +1,47 @@ +/** + * Represents an abstract HTTP API. + */ +export default abstract class HttpApi { + #apiUrl: string + + constructor(apiUrl: string) { + this.#apiUrl = apiUrl + } + + /** + * Factory function for running GET requests + * @param endpoint api endpoint + * @param requestInit additional data passed to request + * @returns api response + */ + protected async getRequest( + endpoint: string, + requestInit?: RequestInit, + ): Promise { + return fetch(new URL(endpoint, this.#apiUrl), { + credentials: "include", + ...requestInit, + }) + } + + /** + * Factory function for running POST requests + * @param endpoint api endpoint + * @param body data passed to POST request + * @param requestInit additional data passed to request + * @returns api response + */ + protected async postRequest( + endpoint: string, + body: unknown, + requestInit?: RequestInit, + ): Promise { + return fetch(new URL(endpoint, this.#apiUrl), { + method: "POST", + body: JSON.stringify(body), + credentials: "include", + headers: { "Content-Type": "application/json" }, + ...requestInit, + }) + } +} From 226465b0827a64768467834090f9fb67c644e74e Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 13:22:20 +0200 Subject: [PATCH 02/51] Implement a class for integration with tBTC API The class integrates with tBTC API to submit deposit reveal data and initiate deposit process. --- sdk/src/api/TbtcApi.ts | 118 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 sdk/src/api/TbtcApi.ts diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/api/TbtcApi.ts new file mode 100644 index 000000000..6f98ee4c3 --- /dev/null +++ b/sdk/src/api/TbtcApi.ts @@ -0,0 +1,118 @@ +import { BitcoinTxHash, BitcoinTxOutpoint } from "@keep-network/tbtc-v2.ts" +import HttpApi from "./HttpApi" + +/** + * Represents a class for integration with tBTC API. + */ +export default class TbtcApi extends HttpApi { + /** + * Register deposit data in the tBTC API. + * @param revealData Deposit data. + * @returns True if the reveal was successfully saved to the database, false + * otherwise. + */ + async saveReveal(revealData: SaveRevealRequest): Promise { + const response = await this.postRequest("reveals", revealData) + + if (!response.ok) + throw new Error( + `Reveal not saved properly in the database, response: ${response.status}`, + ) + + const { success } = (await response.json()) as { success: boolean } + + return success + } + + /** + * Initiate a bitcoin deposit in the tBTC API. + * @param depositData Data of the deposit. + * @returns Details of the initiated deposit. + */ + async createDeposit(depositData: CreateDepositRequest): Promise<{ + depositId: string + depositStatus: DepositStatus + fundingOutpoint: BitcoinTxOutpoint + }> { + const response = await this.postRequest("deposits", depositData) + if (!response.ok) + throw new Error( + `Bitcoin deposit creation failed, response: ${response.status}`, + ) + + const responseData = (await response.json()) as CreateDepositResponse + + return { + depositId: responseData.depositId, + depositStatus: responseData.newDepositStatus, + fundingOutpoint: { + transactionHash: BitcoinTxHash.from(responseData.transactionHash), + outputIndex: responseData.outputIndex, + }, + } + } +} + +/** + * Represents the metadata for a reveal operation. + */ +type RevealMetadata = { + depositOwner: string + referral: number +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the request payload for saving a reveal. + */ +type SaveRevealRequest = { + address: string + revealInfo: BitcoinDepositReceiptJson + metadata: RevealMetadata + application: string +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the JSON structure of a Bitcoin deposit receipt. + */ +type BitcoinDepositReceiptJson = { + depositor: string + blindingFactor: string + walletPublicKeyHash: string + refundPublicKeyHash: string + refundLocktime: string + extraData: string +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the request payload for creating a deposit. + */ +type CreateDepositRequest = { + depositReceipt: BitcoinDepositReceiptJson + depositOwner: string + referral: number +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the status of a deposit. + */ +enum DepositStatus { + Queued = "QUEUED", + Initialized = "INITIALIZED", + Finalized = "FINALIZED", + Cancelled = "CANCELLED", +} + +// TODO: This type is copied from tbtc-api, we should consider exporting it from there. +/** + * Represents the response structure of a deposit creation request. + */ +type CreateDepositResponse = { + depositId: string + newDepositStatus: DepositStatus + transactionHash: string + outputIndex: number +} From c8241361581ac36f3ee2e9696157d78b24842a11 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 14:17:50 +0200 Subject: [PATCH 03/51] Implement a tBTC module to wrap tBTC API interactions The module wraps interactions with tBTC API and tBTC SDK. It should be used by staking flow to initialize a tBTC deposit and submit it to the relayer bot. --- sdk/src/api/TbtcApi.ts | 2 +- sdk/src/lib/utils/backoff.ts | 16 ++++- sdk/src/modules/tbtc/Deposit.ts | 89 +++++++++++++++++++++++++ sdk/src/modules/tbtc/Tbtc.ts | 114 ++++++++++++++++++++++++++++++++ sdk/src/modules/tbtc/index.ts | 5 ++ 5 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 sdk/src/modules/tbtc/Deposit.ts create mode 100644 sdk/src/modules/tbtc/Tbtc.ts create mode 100644 sdk/src/modules/tbtc/index.ts diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/api/TbtcApi.ts index 6f98ee4c3..786e9f0b2 100644 --- a/sdk/src/api/TbtcApi.ts +++ b/sdk/src/api/TbtcApi.ts @@ -65,7 +65,7 @@ type RevealMetadata = { /** * Represents the request payload for saving a reveal. */ -type SaveRevealRequest = { +export type SaveRevealRequest = { address: string revealInfo: BitcoinDepositReceiptJson metadata: RevealMetadata diff --git a/sdk/src/lib/utils/backoff.ts b/sdk/src/lib/utils/backoff.ts index ff5dd6676..3720d8476 100644 --- a/sdk/src/lib/utils/backoff.ts +++ b/sdk/src/lib/utils/backoff.ts @@ -2,4 +2,18 @@ import { backoffRetrier } from "@keep-network/tbtc-v2.ts" type BackoffRetrierParameters = Parameters -export { backoffRetrier, BackoffRetrierParameters } +type RetryOptions = { + /** + * The number of retries to perform before bubbling the failure out. + * @see backoffRetrier for more details. + */ + retires: BackoffRetrierParameters[0] + /** + * Initial backoff step in milliseconds that will be increased exponentially + * for subsequent retry attempts. (default = 1000 ms) + * @see backoffRetrier for more details. + */ + backoffStepMs: BackoffRetrierParameters[1] +} + +export { backoffRetrier, RetryOptions } diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts new file mode 100644 index 000000000..f5322118b --- /dev/null +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -0,0 +1,89 @@ +import { Deposit as TbtcDeposit } from "@keep-network/tbtc-v2.ts" + +import type { DepositReceipt as TbtcSdkDepositReceipt } from "@keep-network/tbtc-v2.ts" +import type { SaveRevealRequest as DepositRevealData } from "../../api/TbtcApi" + +import TbtcApi from "../../api/TbtcApi" + +import { backoffRetrier, RetryOptions } from "../../lib/utils" + +export type DepositReceipt = TbtcSdkDepositReceipt + +/** + * Represents a deposit for the tBTC protocol. + */ +export default class Deposit { + readonly #tbtcApi: TbtcApi + + readonly #tbtcDeposit: TbtcDeposit + + readonly #revealData: DepositRevealData + + constructor( + tbtcApi: TbtcApi, + tbtcDeposit: TbtcDeposit, + revealData: DepositRevealData, + ) { + this.#tbtcApi = tbtcApi + this.#tbtcDeposit = tbtcDeposit + this.#revealData = revealData + } + + /** + * Retrieves the Bitcoin address corresponding to this deposit. + * This address should be used as the destination for sending BTC to fund the + * deposit. + * @returns The Bitcoin address corresponding to this deposit. + */ + async getBitcoinAddress(): Promise { + return this.#tbtcDeposit.getBitcoinAddress() + } + + /** + * Retrieves the receipt corresponding to the tbtc deposit. + * @returns The deposit receipt. + */ + getReceipt(): DepositReceipt { + return this.#tbtcDeposit.getReceipt() + } + + /** + * Waits for the deposit to be funded with BTC. + * @param options The retry options for waiting. + * @throws Error if the deposit is not funded within the specified retries. + */ + async waitForFunding(options: RetryOptions): Promise { + await backoffRetrier( + options.retires, + options.backoffStepMs, + )(async () => { + const utxos = await this.#tbtcDeposit.detectFunding() + + if (utxos.length === 0) throw new Error("Deposit not funded yet") + }) + } + + /** + * Creates a bitcoin deposit on the tBTC API backend side. + * This function should be called after the bitcoin transaction is made to the + * deposit address. + * @throws Error if the deposit creation fails on the tBTC API side or the bitcoin + * funding transaction couldn't be found. + * @returns The tBTC API deposit ID. + */ + async createDeposit(): Promise { + const { revealInfo, metadata } = this.#revealData + const { depositOwner, referral } = metadata + + const createBitcoinDepositData = { + depositReceipt: revealInfo, + depositOwner, + referral, + } + + const response = await this.#tbtcApi.createDeposit(createBitcoinDepositData) + + // TODO: Determine return type based on dApp needs, possibly calculate depositKey. + return response.depositId + } +} diff --git a/sdk/src/modules/tbtc/Tbtc.ts b/sdk/src/modules/tbtc/Tbtc.ts new file mode 100644 index 000000000..007aa74e0 --- /dev/null +++ b/sdk/src/modules/tbtc/Tbtc.ts @@ -0,0 +1,114 @@ +import { ChainIdentifier, TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" + +import TbtcApi from "../../api/TbtcApi" +import { BitcoinDepositor } from "../../lib/contracts" +import { EthereumNetwork } from "../../lib/ethereum" + +import Deposit from "./Deposit" +import { EthereumSignerCompatibleWithEthersV5 } from "../../lib/utils" + +/** + * Represents the tBTC module. + */ +export default class Tbtc { + readonly #tbtcApi: TbtcApi + + readonly #tbtcSdk: TbtcSdk + + readonly #bitcoinDepositor: BitcoinDepositor + + constructor( + tbtcApi: TbtcApi, + tbtcSdk: TbtcSdk, + bitcoinDepositor: BitcoinDepositor, + ) { + this.#tbtcApi = tbtcApi + this.#tbtcSdk = tbtcSdk + this.#bitcoinDepositor = bitcoinDepositor + } + + /** + * Initializes the Tbtc module. + * + * @param signer The Ethereum signer compatible with ethers v5. + * @param network The Ethereum network. + * @param tbtcApiUrl The tBTC API URL. + * @param bitcoinDepositor The Bitcoin depositor contract handle. + * @returns A Promise that resolves to an instance of Tbtc. + */ + static async initialize( + signer: EthereumSignerCompatibleWithEthersV5, + network: EthereumNetwork, + tbtcApiUrl: string, + bitcoinDepositor: BitcoinDepositor, + ): Promise { + const tbtcApi = new TbtcApi(tbtcApiUrl) + + const tbtcSdk = + network === "mainnet" + ? // @ts-expect-error We require the `signer` must include the ethers v5 + // signer's methods used in tBTC-v2.ts SDK so if we pass signer from + // ethers v6 it won't break the Acre SDK initialization. + await TbtcSdk.initializeMainnet(signer) + : // @ts-expect-error We require the `signer` must include the ethers v5 + // signer's methods used in tBTC-v2.ts SDK so if we pass signer from + // ethers v6 it won't break the Acre SDK initialization. + await TbtcSdk.initializeSepolia(signer) + + return new Tbtc(tbtcApi, tbtcSdk, bitcoinDepositor) + } + + /** + * Function to initialize a tBTC deposit. It submits deposit data to the tBTC + * API and returns the deposit object. + * @param depositOwner Ethereum address of the deposit owner. + * @param bitcoinRecoveryAddress P2PKH or P2WPKH Bitcoin address that can be + * used for emergency recovery of the deposited funds. + * @param referral Deposit referral number. + */ + async initiateDeposit( + depositOwner: ChainIdentifier, + bitcoinRecoveryAddress: string, + referral: number, + ): Promise { + if (!depositOwner || !bitcoinRecoveryAddress) { + throw new Error("Ethereum or Bitcoin address is not available") + } + + const extraData = this.#bitcoinDepositor.encodeExtraData( + depositOwner, + referral, + ) + + const tbtcDeposit = await this.#tbtcSdk.deposits.initiateDepositWithProxy( + bitcoinRecoveryAddress, + this.#bitcoinDepositor, + extraData, + ) + + const receipt = tbtcDeposit.getReceipt() + + const revealData = { + address: depositOwner.identifierHex, + revealInfo: { + depositor: receipt.depositor.identifierHex, + blindingFactor: receipt.blindingFactor.toString(), + walletPublicKeyHash: receipt.walletPublicKeyHash.toString(), + refundPublicKeyHash: receipt.refundPublicKeyHash.toString(), + refundLocktime: receipt.refundLocktime.toString(), + extraData: receipt.extraData!.toString(), + }, + metadata: { + depositOwner: depositOwner.identifierHex, + referral, + }, + application: "acre", + } + + const revealSaved: boolean = await this.#tbtcApi.saveReveal(revealData) + if (!revealSaved) + throw new Error("Reveal not saved properly in the database") + + return new Deposit(this.#tbtcApi, tbtcDeposit, revealData) + } +} diff --git a/sdk/src/modules/tbtc/index.ts b/sdk/src/modules/tbtc/index.ts new file mode 100644 index 000000000..435e472f3 --- /dev/null +++ b/sdk/src/modules/tbtc/index.ts @@ -0,0 +1,5 @@ +import Tbtc from "./Tbtc" + +export * from "./Deposit" + +export default Tbtc From 3c08bd00be29996553b4a01a35091817d291128f Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 15:16:39 +0200 Subject: [PATCH 04/51] Use updated TBTC module in staking process The tBTC module wraps tBTC API and tBTC SDK and the staking process should interact with this module. In this commit we update references to match the new deposit initialization in tBTC with tBTC API instead of the custom DepositorProxy implementation. Wait for for funding transaction helper function was moved to the tBTC module already so here we remove it from the staking flow. --- sdk/src/lib/contracts/bitcoin-depositor.ts | 2 - sdk/src/lib/ethereum/bitcoin-depositor.ts | 33 ++---------- sdk/src/modules/staking/index.ts | 22 ++++---- .../modules/staking/stake-initialization.ts | 51 ++++--------------- 4 files changed, 24 insertions(+), 84 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index baa71a770..2b31f78d9 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -2,8 +2,6 @@ import { Hex } from "../utils" import { ChainIdentifier } from "./chain-identifier" import { DepositorProxy } from "./depositor-proxy" -export { DepositReceipt } from "@keep-network/tbtc-v2.ts" - export type DecodedExtraData = { depositOwner: ChainIdentifier referral: number diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 52f634c24..f7ebd36b7 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -15,10 +15,9 @@ import { ChainIdentifier, DecodedExtraData, BitcoinDepositor, - DepositReceipt, DepositFees, } from "../contracts" -import { BitcoinRawTxVectors } from "../bitcoin" + import { EthereumAddress } from "./address" import { EthersContractConfig, @@ -89,33 +88,9 @@ class EthereumBitcoinDepositor return EthereumAddress.from(vault) } - /** - * @see {BitcoinDepositor#revealDeposit} - */ - async revealDeposit( - depositTx: BitcoinRawTxVectors, - depositOutputIndex: number, - deposit: DepositReceipt, - ): Promise { - const { fundingTx, reveal, extraData } = packRevealDepositParameters( - depositTx, - depositOutputIndex, - deposit, - await this.getTbtcVaultChainIdentifier(), - ) - - if (!extraData) throw new Error("Invalid extra data") - - const { depositOwner, referral } = this.decodeExtraData(extraData) - - const tx = await this.instance.initializeDeposit( - fundingTx, - reveal, - `0x${depositOwner.identifierHex}`, - referral, - ) - - return Hex.from(tx.hash) + // eslint-disable-next-line class-methods-use-this + revealDeposit(): Promise { + throw new Error("Unsupported") } /** diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 32e8b5363..e4e5c8c3b 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -1,8 +1,9 @@ -import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" -import { AcreContracts, DepositorProxy, DepositFees } from "../../lib/contracts" +import { ChainIdentifier } from "@keep-network/tbtc-v2.ts" +import { AcreContracts, DepositFees } from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" import { fromSatoshi, toSatoshi } from "../../lib/utils" +import Tbtc from "../tbtc" /** * Represents all total deposit fees grouped by network. @@ -28,14 +29,14 @@ class StakingModule { readonly #messageSigner: ChainEIP712Signer /** - * tBTC SDK. + * tBTC Module. */ - readonly #tbtc: TBTC + readonly #tbtc: Tbtc constructor( _contracts: AcreContracts, _messageSigner: ChainEIP712Signer, - _tbtc: TBTC, + _tbtc: Tbtc, ) { this.#contracts = _contracts this.#messageSigner = _messageSigner @@ -54,14 +55,13 @@ class StakingModule { */ async initializeStake( bitcoinRecoveryAddress: string, - staker: ChainIdentifier, + staker: ChainIdentifier, // TODO: We should resolve the address with OrangeKit SDK referral: number, - depositorProxy?: DepositorProxy, ) { - const deposit = await this.#tbtc.deposits.initiateDepositWithProxy( + const tbtcDeposit = await this.#tbtc.initiateDeposit( + staker, bitcoinRecoveryAddress, - depositorProxy ?? this.#contracts.bitcoinDepositor, - this.#contracts.bitcoinDepositor.encodeExtraData(staker, referral), + referral, ) return new StakeInitialization( @@ -69,7 +69,7 @@ class StakingModule { this.#messageSigner, bitcoinRecoveryAddress, staker, - deposit, + tbtcDeposit, ) } diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 9ed9b31a5..9b988b67f 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -1,4 +1,7 @@ -import { Deposit as TbtcDeposit } from "@keep-network/tbtc-v2.ts" +import TbtcDeposit from "../tbtc/Deposit" + +import type { DepositReceipt } from "../tbtc" + import { ChainEIP712Signer, ChainSignedMessage, @@ -6,28 +9,8 @@ import { Message, Types, } from "../../lib/eip712-signer" -import { - AcreContracts, - DepositReceipt, - ChainIdentifier, -} from "../../lib/contracts" -import { Hex, backoffRetrier, BackoffRetrierParameters } from "../../lib/utils" +import { AcreContracts, ChainIdentifier } from "../../lib/contracts" -type StakeOptions = { - /** - * The number of retries to perform before bubbling the failure out. - * @see backoffRetrier for more details. - */ - retires: BackoffRetrierParameters[0] - /** - * Initial backoff step in milliseconds that will be increased exponentially - * for subsequent retry attempts. (default = 1000 ms) - * @see backoffRetrier for more details. - */ - backoffStepMs: BackoffRetrierParameters[1] -} - -// TODO: Rename to `DepositInitialization` to be consistent with the naming. /** * Represents an instance of the staking flow. Staking flow requires a few steps * which should be done to stake BTC. @@ -44,7 +27,7 @@ class StakeInitialization { readonly #messageSigner: ChainEIP712Signer /** - * Component representing an instance of the tBTC v2 deposit process. + * Component representing an instance of the tBTC deposit process. */ readonly #tbtcDeposit: TbtcDeposit @@ -158,30 +141,14 @@ class StakeInitialization { * @see StakeOptions for more details. * @returns Transaction hash of the stake initiation transaction. */ - async stake( - options: StakeOptions = { retires: 5, backoffStepMs: 5_000 }, - ): Promise { + async stake(options = { retires: 5, backoffStepMs: 5_000 }): Promise { if (!this.#signedMessage) { throw new Error("Sign message first") } - await this.#waitForBitcoinFundingTx(options) - - return this.#tbtcDeposit.initiateMinting() - } + await this.#tbtcDeposit.waitForFunding(options) - async #waitForBitcoinFundingTx({ - retires, - backoffStepMs, - }: StakeOptions): Promise { - await backoffRetrier( - retires, - backoffStepMs, - )(async () => { - const utxos = await this.#tbtcDeposit.detectFunding() - - if (utxos.length === 0) throw new Error("Deposit not funded yet") - }) + return this.#tbtcDeposit.createDeposit() } } From a1095d7fb42f2049c7feb6bb0b3b466a1186b115 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Wed, 24 Apr 2024 15:27:51 +0200 Subject: [PATCH 05/51] Update Acre SDK initialization for tBTC module The tBTC module initialization was updated to match the tBTC module wrapper for tBTC SDK. --- sdk/src/acre.ts | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/sdk/src/acre.ts b/sdk/src/acre.ts index f7458d78d..912364816 100644 --- a/sdk/src/acre.ts +++ b/sdk/src/acre.ts @@ -1,4 +1,3 @@ -import { TBTC } from "@keep-network/tbtc-v2.ts" import { AcreContracts } from "./lib/contracts" import { ChainEIP712Signer } from "./lib/eip712-signer" import { @@ -7,10 +6,13 @@ import { getEthereumContracts, } from "./lib/ethereum" import { StakingModule } from "./modules/staking" +import Tbtc from "./modules/tbtc" import { EthereumSignerCompatibleWithEthersV5 } from "./lib/utils" +const TBTC_API_ENDPOINT = process.env.TBTC_API_ENDPOINT ?? "" + class Acre { - readonly #tbtc: TBTC + readonly #tbtc: Tbtc readonly #messageSigner: ChainEIP712Signer @@ -21,7 +23,7 @@ class Acre { constructor( _contracts: AcreContracts, _messageSigner: ChainEIP712Signer, - _tbtc: TBTC, + _tbtc: Tbtc, ) { this.contracts = _contracts this.#tbtc = _tbtc @@ -37,30 +39,17 @@ class Acre { signer: EthereumSignerCompatibleWithEthersV5, network: EthereumNetwork, ): Promise { - const tbtc = await Acre.#getTBTCEthereumSDK(signer, network) const contracts = getEthereumContracts(signer, network) const messages = new EthereumEIP712Signer(signer) - return new Acre(contracts, messages, tbtc) - } + const tbtc = await Tbtc.initialize( + signer, + network, + TBTC_API_ENDPOINT, + contracts.bitcoinDepositor, + ) - static #getTBTCEthereumSDK( - signer: EthereumSignerCompatibleWithEthersV5, - network: EthereumNetwork, - ): Promise { - switch (network) { - case "sepolia": - // @ts-expect-error We require the `signer` must include the ether v5 - // signer's methods used in tBTC-v2.ts SDK so if we pass signer from - // ethers v6 it won't break the Acre SDK initialization. - return TBTC.initializeSepolia(signer) - case "mainnet": - default: - // @ts-expect-error We require the `signer` must include the ether v5 - // signer's methods used in tBTC-v2.ts SDK so if we pass signer from - // ethers v6 it won't break the Acre SDK initialization. - return TBTC.initializeMainnet(signer) - } + return new Acre(contracts, messages, tbtc) } } From c3d4948faffcc9da928264cb9618e7adad19f7c8 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 26 Apr 2024 14:11:43 +0200 Subject: [PATCH 06/51] Implement `GrantedSeasonPassCard` component --- .../OverviewPage/GrantedSeasonPassCard.tsx | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx diff --git a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx new file mode 100644 index 000000000..845819dc2 --- /dev/null +++ b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx @@ -0,0 +1,38 @@ +import React from "react" +import { Card, CardBody, CardHeader, CardProps, Icon } from "@chakra-ui/react" +import { IconDiscountCheckFilled, IconLock } from "@tabler/icons-react" + +type GrantedSeasonPassCardProps = CardProps & { + heading: string +} + +export function GrantedSeasonPassCard(props: GrantedSeasonPassCardProps) { + const { heading, ...restProps } = props + + return ( + + + {heading} + + + + + Your seat is secured. + + + ) +} From 8f8b1e85db55523a99eabf1709b1ad2e5d82971d Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 26 Apr 2024 15:31:36 +0200 Subject: [PATCH 07/51] Implement `DashboardCard` component base structure --- .../DashboardCard/DashboardCard.tsx | 62 +++++++++++++++++++ .../pages/OverviewPage/DashboardCard/index.ts | 1 + dapp/src/pages/OverviewPage/index.tsx | 7 +++ 3 files changed, 70 insertions(+) create mode 100644 dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx create mode 100644 dapp/src/pages/OverviewPage/DashboardCard/index.ts diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx new file mode 100644 index 000000000..67e2e57a3 --- /dev/null +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -0,0 +1,62 @@ +import React from "react" +import { + Button, + CardHeader, + CardBody, + Card, + CardProps, + Tag, + HStack, + VStack, +} from "@chakra-ui/react" +import { TextMd } from "#/components/shared/Typography" +import { + CurrencyBalance, + CurrencyBalanceProps, +} from "#/components/shared/CurrencyBalance" + +type AmountType = CurrencyBalanceProps["amount"] +type DashboardCardProps = CardProps & { + bitcoinAmount: AmountType + usdAmount: AmountType + // TODO: Make this required in post MVP (ternary operator below should be + // removed) + positionPercentage?: number +} + +export default function DashboardCard(props: DashboardCardProps) { + const { bitcoinAmount, usdAmount, positionPercentage, ...restProps } = props + return ( + + + {positionPercentage ? ( + + My position Top {positionPercentage}% + + ) : ( + // TODO: Update when designer provides alternative copy + Dashboard + )} + + + + + + + + + Rewards Boost + + + + + + + + + ) +} diff --git a/dapp/src/pages/OverviewPage/DashboardCard/index.ts b/dapp/src/pages/OverviewPage/DashboardCard/index.ts new file mode 100644 index 000000000..45e4d8a2c --- /dev/null +++ b/dapp/src/pages/OverviewPage/DashboardCard/index.ts @@ -0,0 +1 @@ +export { default as DashboardCard } from "./DashboardCard" diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 2a7433b92..669da07b1 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -8,8 +8,15 @@ import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { DocsCard } from "./DocsCard" import { ActivityCarousel } from "./ActivityCarousel" +import { DashboardCard } from "./DashboardCard" export default function OverviewPage() { + return ( + + + + ) + return ( From 8133d877096d26436a82293cd58980d5abe5d7f1 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 26 Apr 2024 17:34:14 +0200 Subject: [PATCH 08/51] Style `DashboardCard` component --- .../DashboardCard/DashboardCard.tsx | 63 ++++++++++++++++--- dapp/src/pages/OverviewPage/index.tsx | 6 +- dapp/src/theme/Button.ts | 8 +++ 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index 67e2e57a3..e3b72ac3e 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -30,31 +30,78 @@ export default function DashboardCard(props: DashboardCardProps) { {positionPercentage ? ( - - My position Top {positionPercentage}% + + My position + + Top {positionPercentage}% + ) : ( // TODO: Update when designer provides alternative copy - Dashboard + Dashboard )} - + - Rewards Boost + + Rewards Boost + - - - + + + diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 669da07b1..876e535a0 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -13,7 +13,11 @@ import { DashboardCard } from "./DashboardCard" export default function OverviewPage() { return ( - + ) diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index ccb108bd7..959171596 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -16,6 +16,14 @@ export const buttonTheme: ComponentSingleStyleConfig = { py: "1rem", borderRadius: "lg", }, + xl: { + fontSize: "md", + fontWeight: "bold", + lineHeight: 6, + py: 4, + px: 7, + borderRadius: "lg", + }, }, variants: { solid: { From a1cf7efab9bcd312de5987a03cfd4155ed801af3 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 26 Apr 2024 18:11:44 +0200 Subject: [PATCH 09/51] Add `IconTag` component Added `BoostArrow` icon --- dapp/src/assets/icons/BoostArrow.tsx | 303 ++++++++++++++++++ dapp/src/components/shared/IconTag.tsx | 27 ++ .../DashboardCard/DashboardCard.tsx | 17 +- 3 files changed, 335 insertions(+), 12 deletions(-) create mode 100644 dapp/src/assets/icons/BoostArrow.tsx create mode 100644 dapp/src/components/shared/IconTag.tsx diff --git a/dapp/src/assets/icons/BoostArrow.tsx b/dapp/src/assets/icons/BoostArrow.tsx new file mode 100644 index 000000000..7f94b0d6e --- /dev/null +++ b/dapp/src/assets/icons/BoostArrow.tsx @@ -0,0 +1,303 @@ +import React from "react" +import { createIcon } from "@chakra-ui/react" + +export const BoostArrow = createIcon({ + displayName: "BoostArrow", + viewBox: "0 0 230 300", + defaultProps: { w: 230, h: 300 }, + path: [ + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + + + + + + + + + + + + + + + + + + + + + + , + ], +}) diff --git a/dapp/src/components/shared/IconTag.tsx b/dapp/src/components/shared/IconTag.tsx new file mode 100644 index 000000000..7c2f8b51e --- /dev/null +++ b/dapp/src/components/shared/IconTag.tsx @@ -0,0 +1,27 @@ +import React from "react" +import { Tag, TagLeftIcon, TagLabel, TagProps, Icon } from "@chakra-ui/react" + +type IconTagProps = TagProps & { + icon: typeof Icon +} + +export default function IconTag(props: IconTagProps) { + const { children, icon, ...restProps } = props + + return ( + + + {children} + + ) +} diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index e3b72ac3e..ebd83ec31 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import React from "react" import { Button, @@ -14,6 +15,8 @@ import { CurrencyBalance, CurrencyBalanceProps, } from "#/components/shared/CurrencyBalance" +import IconTag from "#/components/shared/IconTag" +import { BoostArrow } from "#/assets/icons/BoostArrow" type AmountType = CurrencyBalanceProps["amount"] type DashboardCardProps = CardProps & { @@ -72,18 +75,8 @@ export default function DashboardCard(props: DashboardCardProps) { /> - - Rewards Boost - + {/* // TODO: Bring it back in post MVP phases */} + {/* Rewards Boost */} From 6131faff55ab9923bc68274b4e627c681c1f07e1 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 26 Apr 2024 18:17:24 +0200 Subject: [PATCH 10/51] Bring back page content --- dapp/src/pages/OverviewPage/index.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 876e535a0..2a7433b92 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -8,19 +8,8 @@ import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { DocsCard } from "./DocsCard" import { ActivityCarousel } from "./ActivityCarousel" -import { DashboardCard } from "./DashboardCard" export default function OverviewPage() { - return ( - - - - ) - return ( From 468377749a0472894ef396c05ee3fcf4888d34a4 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Mon, 29 Apr 2024 17:07:08 +0200 Subject: [PATCH 11/51] Apply updates after consultation with designer --- .../DashboardCard/DashboardCard.tsx | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index ebd83ec31..a91025507 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -22,9 +22,7 @@ type AmountType = CurrencyBalanceProps["amount"] type DashboardCardProps = CardProps & { bitcoinAmount: AmountType usdAmount: AmountType - // TODO: Make this required in post MVP (ternary operator below should be - // removed) - positionPercentage?: number + positionPercentage?: number // TODO: Make this required in post MVP phase } export default function DashboardCard(props: DashboardCardProps) { @@ -32,9 +30,9 @@ export default function DashboardCard(props: DashboardCardProps) { return ( - {positionPercentage ? ( - - My position + + My position + {positionPercentage && ( Top {positionPercentage}% - - ) : ( - // TODO: Update when designer provides alternative copy - Dashboard - )} + )} + @@ -75,8 +70,7 @@ export default function DashboardCard(props: DashboardCardProps) { /> - {/* // TODO: Bring it back in post MVP phases */} - {/* Rewards Boost */} + Rewards Boost From acc5b349407f3a228a54cd3afda03e8fc53195f5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:09:35 +0200 Subject: [PATCH 12/51] Rename TbtcDeposit to TbtcSdkDeposit The name better reflect that the Deposit object is from tbtc sdk library. --- sdk/src/modules/tbtc/Deposit.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts index f5322118b..81de4b999 100644 --- a/sdk/src/modules/tbtc/Deposit.ts +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -1,4 +1,4 @@ -import { Deposit as TbtcDeposit } from "@keep-network/tbtc-v2.ts" +import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" import type { DepositReceipt as TbtcSdkDepositReceipt } from "@keep-network/tbtc-v2.ts" import type { SaveRevealRequest as DepositRevealData } from "../../api/TbtcApi" @@ -15,17 +15,17 @@ export type DepositReceipt = TbtcSdkDepositReceipt export default class Deposit { readonly #tbtcApi: TbtcApi - readonly #tbtcDeposit: TbtcDeposit + readonly #tbtcSdkDeposit: TbtcSdkDeposit readonly #revealData: DepositRevealData constructor( tbtcApi: TbtcApi, - tbtcDeposit: TbtcDeposit, + tbtcSdkDeposit: TbtcSdkDeposit, revealData: DepositRevealData, ) { this.#tbtcApi = tbtcApi - this.#tbtcDeposit = tbtcDeposit + this.#tbtcSdkDeposit = tbtcSdkDeposit this.#revealData = revealData } @@ -36,7 +36,7 @@ export default class Deposit { * @returns The Bitcoin address corresponding to this deposit. */ async getBitcoinAddress(): Promise { - return this.#tbtcDeposit.getBitcoinAddress() + return this.#tbtcSdkDeposit.getBitcoinAddress() } /** @@ -44,7 +44,7 @@ export default class Deposit { * @returns The deposit receipt. */ getReceipt(): DepositReceipt { - return this.#tbtcDeposit.getReceipt() + return this.#tbtcSdkDeposit.getReceipt() } /** @@ -57,7 +57,7 @@ export default class Deposit { options.retires, options.backoffStepMs, )(async () => { - const utxos = await this.#tbtcDeposit.detectFunding() + const utxos = await this.#tbtcSdkDeposit.detectFunding() if (utxos.length === 0) throw new Error("Deposit not funded yet") }) From a7d3213581782163eeab89ec0614fb55626b9484 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:10:39 +0200 Subject: [PATCH 13/51] Fix typo in retries property name --- sdk/src/lib/utils/backoff.ts | 2 +- sdk/src/modules/staking/stake-initialization.ts | 2 +- sdk/src/modules/tbtc/Deposit.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/lib/utils/backoff.ts b/sdk/src/lib/utils/backoff.ts index 3720d8476..5643453f5 100644 --- a/sdk/src/lib/utils/backoff.ts +++ b/sdk/src/lib/utils/backoff.ts @@ -7,7 +7,7 @@ type RetryOptions = { * The number of retries to perform before bubbling the failure out. * @see backoffRetrier for more details. */ - retires: BackoffRetrierParameters[0] + retries: BackoffRetrierParameters[0] /** * Initial backoff step in milliseconds that will be increased exponentially * for subsequent retry attempts. (default = 1000 ms) diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 9b988b67f..12d3406e1 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -141,7 +141,7 @@ class StakeInitialization { * @see StakeOptions for more details. * @returns Transaction hash of the stake initiation transaction. */ - async stake(options = { retires: 5, backoffStepMs: 5_000 }): Promise { + async stake(options = { retries: 5, backoffStepMs: 5_000 }): Promise { if (!this.#signedMessage) { throw new Error("Sign message first") } diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts index 81de4b999..dd3011eed 100644 --- a/sdk/src/modules/tbtc/Deposit.ts +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -54,7 +54,7 @@ export default class Deposit { */ async waitForFunding(options: RetryOptions): Promise { await backoffRetrier( - options.retires, + options.retries, options.backoffStepMs, )(async () => { const utxos = await this.#tbtcSdkDeposit.detectFunding() From ffb0d7c04a819b27ea28f906155fbe545a40ee8c Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:13:08 +0200 Subject: [PATCH 14/51] Remove revealDeposit tests The revealDeposit is no longer implemented by the bitcoin depositor contract implementation as reveals are handled by the tbtc API bot. --- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 117 ------------------- 1 file changed, 117 deletions(-) diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 882ea534a..eac5c3446 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -2,7 +2,6 @@ import ethers, { Contract, ZeroAddress, getAddress } from "ethers" import { EthereumBitcoinDepositor, EthereumAddress, - Hex, EthereumSigner, DepositFees, } from "../../../src" @@ -85,122 +84,6 @@ describe("BitcoinDepositor", () => { }) }) - describe("revealDeposit", () => { - const depositOwner = EthereumAddress.from( - "0x000055d85E80A49B5930C4a77975d44f012D86C1", - ) - const bitcoinFundingTransaction = { - version: Hex.from("00000000"), - inputs: Hex.from("11111111"), - outputs: Hex.from("22222222"), - locktime: Hex.from("33333333"), - } - const depositReveal = { - fundingOutputIndex: 2, - walletPublicKeyHash: Hex.from("8db50eb52063ea9d98b3eac91489a90f738986f6"), - refundPublicKeyHash: Hex.from("28e081f285138ccbe389c1eb8985716230129f89"), - blindingFactor: Hex.from("f9f0c90d00039523"), - refundLocktime: Hex.from("60bcea61"), - depositor: depositOwner, - } - describe("when extra data is defined", () => { - const extraData = { - depositOwner, - referral: 6851, - hex: Hex.from( - "0x000055d85e80a49b5930c4a77975d44f012d86c11ac300000000000000000000", - ), - } - - const depositWithExtraData = { - ...depositReveal, - extraData: extraData.hex, - } - - const { referral } = extraData - - const mockedTx = Hex.from( - "0483fe6a05f245bccc7b10085f3c4d282d87ca42f27437d077acfd75e91183a0", - ) - let result: Hex - - beforeAll(async () => { - mockedContractInstance.initializeDeposit.mockReturnValue({ - hash: mockedTx.toPrefixedString(), - }) - - const { fundingOutputIndex, ...restDepositData } = depositWithExtraData - - result = await depositor.revealDeposit( - bitcoinFundingTransaction, - fundingOutputIndex, - restDepositData, - ) - }) - - it("should get the vault address", () => { - expect(mockedContractInstance.tbtcVault).toHaveBeenCalled() - }) - - it("should decode extra data", () => { - expect(spyOnEthersDataSlice).toHaveBeenNthCalledWith( - 1, - extraData.hex.toPrefixedString(), - 0, - 20, - ) - expect(spyOnEthersDataSlice).toHaveBeenNthCalledWith( - 2, - extraData.hex.toPrefixedString(), - 20, - 22, - ) - }) - - it("should initialize deposit", () => { - const btcTxInfo = { - version: bitcoinFundingTransaction.version.toPrefixedString(), - inputVector: bitcoinFundingTransaction.inputs.toPrefixedString(), - outputVector: bitcoinFundingTransaction.outputs.toPrefixedString(), - locktime: bitcoinFundingTransaction.locktime.toPrefixedString(), - } - - const revealInfo = { - fundingOutputIndex: depositReveal.fundingOutputIndex, - blindingFactor: depositReveal.blindingFactor.toPrefixedString(), - walletPubKeyHash: - depositReveal.walletPublicKeyHash.toPrefixedString(), - refundPubKeyHash: - depositReveal.refundPublicKeyHash.toPrefixedString(), - refundLocktime: depositReveal.refundLocktime.toPrefixedString(), - vault: `0x${vaultAddress.identifierHex}`, - } - - expect(mockedContractInstance.initializeDeposit).toHaveBeenCalledWith( - btcTxInfo, - revealInfo, - `0x${depositOwner.identifierHex}`, - referral, - ) - expect(result.toPrefixedString()).toBe(mockedTx.toPrefixedString()) - }) - }) - - describe("when extra data not defined", () => { - it("should throw an error", async () => { - const { fundingOutputIndex, ...restDepositData } = depositReveal - - await expect( - depositor.revealDeposit( - bitcoinFundingTransaction, - fundingOutputIndex, - restDepositData, - ), - ).rejects.toThrow("Invalid extra data") - }) - }) - }) - describe("encodeExtraData", () => { const spyOnSolidityPacked = jest.spyOn(ethers, "solidityPacked") From b0228d80ee9f30d7ed53a977fb681fab1ffaef94 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:35:09 +0200 Subject: [PATCH 15/51] Remove unused parameter from docs --- sdk/src/modules/staking/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index e4e5c8c3b..3fa3e4375 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -50,7 +50,6 @@ class StakingModule { * funds. * @param staker The address to which the stBTC shares will be minted. * @param referral Data used for referral program. - * @param depositorProxy Depositor proxy used to initiate the deposit. * @returns Object represents the staking process. */ async initializeStake( From 0e05c20a859054343c08e94296332291302a5f74 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:45:09 +0200 Subject: [PATCH 16/51] Update unit tests for tbtc module Here we update unit tests to reflect recent changes to the tbtc module. --- sdk/test/data/deposit.ts | 62 +++++++++ sdk/test/modules/staking.test.ts | 156 ++++------------------ sdk/test/modules/tbtc/Deposit.test.ts | 106 +++++++++++++++ sdk/test/modules/tbtc/Tbtc.test.ts | 178 ++++++++++++++++++++++++++ sdk/test/utils/mock-tbtc-sdk.ts | 33 +++++ sdk/test/utils/mock-tbtc.ts | 38 ++---- 6 files changed, 414 insertions(+), 159 deletions(-) create mode 100644 sdk/test/data/deposit.ts create mode 100644 sdk/test/modules/tbtc/Deposit.test.ts create mode 100644 sdk/test/modules/tbtc/Tbtc.test.ts create mode 100644 sdk/test/utils/mock-tbtc-sdk.ts diff --git a/sdk/test/data/deposit.ts b/sdk/test/data/deposit.ts new file mode 100644 index 000000000..42905923b --- /dev/null +++ b/sdk/test/data/deposit.ts @@ -0,0 +1,62 @@ +import { BitcoinTxHash, EthereumAddress, Hex } from "@keep-network/tbtc-v2.ts" +import { BigNumber } from "@ethersproject/bignumber" +import type { ChainIdentifier } from "../../src/lib/contracts" + +import type { DepositReceipt } from "../../src/modules/tbtc" +import type { SaveRevealRequest } from "../../src/api/TbtcApi" + +const depositTestData: { + depositOwner: ChainIdentifier + bitcoinRecoveryAddress: string + referral: number + extraData: Hex +} = { + depositOwner: EthereumAddress.from( + "0xa9B38eA6435c8941d6eDa6a46b68E3e211719699", + ), + bitcoinRecoveryAddress: "mjc2zGWypwpNyDi4ZxGbBNnUA84bfgiwYc", + referral: 23505, + extraData: Hex.from( + "0xa9b38ea6435c8941d6eda6a46b68e3e2117196995bd100000000000000000000", + ), +} + +const receiptTestData: DepositReceipt = { + depositor: EthereumAddress.from("0x2cd680318747b720d67bf4246eb7403b476adb34"), + blindingFactor: Hex.from("0xf9f0c90d00039523"), + walletPublicKeyHash: Hex.from("0x8db50eb52063ea9d98b3eac91489a90f738986f6"), + refundPublicKeyHash: Hex.from("0x28e081f285138ccbe389c1eb8985716230129f89"), + refundLocktime: Hex.from("0x60bcea61"), + extraData: depositTestData.extraData, +} + +const revealTestData: SaveRevealRequest = { + address: depositTestData.depositOwner.identifierHex, + revealInfo: { + depositor: receiptTestData.depositor.identifierHex, + blindingFactor: receiptTestData.blindingFactor.toString(), + walletPublicKeyHash: receiptTestData.walletPublicKeyHash.toString(), + refundPublicKeyHash: receiptTestData.refundPublicKeyHash.toString(), + refundLocktime: receiptTestData.refundLocktime.toString(), + extraData: depositTestData.extraData.toString(), + }, + metadata: { + depositOwner: depositTestData.depositOwner.identifierHex, + referral: depositTestData.referral, + }, + application: "acre", +} + +const fundingUtxo: { + transactionHash: BitcoinTxHash + outputIndex: number + value: BigNumber +} = { + transactionHash: BitcoinTxHash.from( + "2f952bdc206bf51bb745b967cb7166149becada878d3191ffe341155ebcd4883", + ), + outputIndex: 1, + value: BigNumber.from(3933200), +} + +export { depositTestData, receiptTestData, revealTestData, fundingUtxo } diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index 81909295e..f269b2d31 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -5,8 +5,6 @@ import { StakingModule, Hex, StakeInitialization, - DepositorProxy, - DepositReceipt, EthereumAddress, DepositFees, DepositFee, @@ -14,7 +12,8 @@ import { import * as satoshiConverter from "../../src/lib/utils/satoshi-converter" import { MockAcreContracts } from "../utils/mock-acre-contracts" import { MockMessageSigner } from "../utils/mock-message-signer" -import { MockTBTC } from "../utils/mock-tbtc" +import { MockTbtc } from "../utils/mock-tbtc" +import { DepositReceipt } from "../../src/modules/tbtc" const stakingModuleData: { initializeStake: { @@ -73,6 +72,7 @@ const stakingModuleData: { const stakingInitializationData: { depositReceipt: Omit mockedInitializeTxHash: Hex + mockedDepositId: string fundingUtxo: { transactionHash: BitcoinTxHash outputIndex: number @@ -87,6 +87,7 @@ const stakingInitializationData: { extraData: stakingModuleData.initializeStake.extraData, }, mockedInitializeTxHash: Hex.from("999999"), + mockedDepositId: "deposit-id-1234", fundingUtxo: { transactionHash: BitcoinTxHash.from( "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", @@ -99,7 +100,7 @@ const stakingInitializationData: { describe("Staking", () => { const contracts: AcreContracts = new MockAcreContracts() const messageSigner = new MockMessageSigner() - const tbtc = new MockTBTC() + const tbtc = new MockTbtc() const staking: StakingModule = new StakingModule( contracts, @@ -115,11 +116,14 @@ describe("Staking", () => { referral, extraData, } = stakingModuleData.initializeStake + + const { mockedDepositId } = stakingInitializationData + const mockedDeposit = { getBitcoinAddress: jest.fn().mockResolvedValue(mockedDepositBTCAddress), - detectFunding: jest.fn(), + waitForFunding: jest.fn(), getReceipt: jest.fn().mockReturnValue({ extraData }), - initiateMinting: jest.fn(), + createDeposit: jest.fn().mockReturnValue(mockedDepositId), } describe("with default depositor proxy implementation", () => { @@ -136,9 +140,7 @@ describe("Staking", () => { .fn() .mockReturnValue(extraData) - tbtc.deposits.initiateDepositWithProxy = jest - .fn() - .mockReturnValue(mockedDeposit) + tbtc.initiateDeposit = jest.fn().mockReturnValue(mockedDeposit) messageSigner.sign = jest.fn().mockResolvedValue(mockedSignedMessage) @@ -149,15 +151,11 @@ describe("Staking", () => { ) }) - it("should encode extra data", () => { - expect(contracts.bitcoinDepositor.encodeExtraData(staker, referral)) - }) - it("should initiate tBTC deposit", () => { - expect(tbtc.deposits.initiateDepositWithProxy).toHaveBeenCalledWith( + expect(tbtc.initiateDeposit).toHaveBeenCalledWith( + staker, bitcoinRecoveryAddress, - contracts.bitcoinDepositor, - extraData, + referral, ) }) @@ -176,7 +174,7 @@ describe("Staking", () => { mockedDeposit.getReceipt.mockReturnValue(depositReceipt) }) - describe("getDepositAddress", () => { + describe("getBitcoinAddress", () => { it("should return bitcoin deposit address", async () => { expect(await result.getBitcoinAddress()).toBe( mockedDepositBTCAddress, @@ -261,136 +259,30 @@ describe("Staking", () => { }) describe("when message has already been signed", () => { - let tx: Hex - const { mockedInitializeTxHash: mockedTxHash, fundingUtxo } = - stakingInitializationData + let depositId: string beforeAll(async () => { - mockedDeposit.initiateMinting.mockResolvedValue(mockedTxHash) - mockedDeposit.detectFunding.mockResolvedValue([fundingUtxo]) + mockedDeposit.waitForFunding.mockResolvedValue(undefined) await result.signMessage() - tx = await result.stake() + depositId = await result.stake() }) - it("should stake tokens via tbtc depositor proxy", () => { - expect(mockedDeposit.initiateMinting).toHaveBeenCalled() + it("should wait for funding", () => { + expect(mockedDeposit.waitForFunding).toHaveBeenCalled() }) - it("should return transaction hash", () => { - expect(tx).toBe(mockedTxHash) - }) - }) - - describe("when waiting for bitcoin deposit tx", () => { - const { mockedInitializeTxHash: mockedTxHash } = - stakingInitializationData - - describe("when can't find transaction after max number of retries", () => { - beforeEach(async () => { - jest.useFakeTimers() - - mockedDeposit.initiateMinting.mockResolvedValue(mockedTxHash) - mockedDeposit.detectFunding.mockClear() - mockedDeposit.detectFunding.mockResolvedValue([]) - - await result.signMessage() - }) - - it("should throw an error", async () => { - // eslint-disable-next-line no-void - void expect(result.stake()).rejects.toThrow( - "Deposit not funded yet", - ) - - await jest.runAllTimersAsync() - - expect(mockedDeposit.detectFunding).toHaveBeenCalledTimes(6) - }) + it("should create the deposit", () => { + expect(mockedDeposit.createDeposit).toHaveBeenCalled() }) - describe("when the funding tx is available", () => { - const { fundingUtxo } = stakingInitializationData - let txPromise: Promise - - beforeAll(async () => { - jest.useFakeTimers() - - mockedDeposit.initiateMinting.mockResolvedValue(mockedTxHash) - - mockedDeposit.detectFunding.mockClear() - mockedDeposit.detectFunding - // First attempt. Deposit not funded yet. - .mockResolvedValueOnce([]) - // Second attempt. Deposit funded. - .mockResolvedValueOnce([fundingUtxo]) - - await result.signMessage() - - txPromise = result.stake() - - await jest.runAllTimersAsync() - }) - - it("should wait for deposit transaction", () => { - expect(mockedDeposit.detectFunding).toHaveBeenCalledTimes(2) - }) - - it("should stake tokens via tbtc depositor proxy", () => { - expect(mockedDeposit.initiateMinting).toHaveBeenCalled() - }) - - it("should return transaction hash", async () => { - const txHash = await txPromise - - expect(txHash).toBe(mockedTxHash) - }) + it("should return deposit id", () => { + expect(depositId).toBe(mockedDepositId) }) }) }) }) }) - - describe("with custom depositor proxy", () => { - const customDepositorProxy: DepositorProxy = { - getChainIdentifier: jest.fn(), - revealDeposit: jest.fn(), - } as unknown as DepositorProxy - - let result: StakeInitialization - - beforeEach(async () => { - contracts.bitcoinDepositor.encodeExtraData = jest - .fn() - .mockReturnValue(extraData) - - tbtc.deposits.initiateDepositWithProxy = jest - .fn() - .mockReturnValue(mockedDeposit) - - result = await staking.initializeStake( - bitcoinRecoveryAddress, - staker, - referral, - customDepositorProxy, - ) - }) - - it("should initiate tBTC deposit", () => { - expect(tbtc.deposits.initiateDepositWithProxy).toHaveBeenCalledWith( - bitcoinRecoveryAddress, - customDepositorProxy, - extraData, - ) - }) - - it("should return stake initialization object", () => { - expect(result).toBeInstanceOf(StakeInitialization) - expect(result.getBitcoinAddress).toBeDefined() - expect(result.stake).toBeDefined() - expect(result.signMessage).toBeDefined() - }) - }) }) describe("sharesBalance", () => { diff --git a/sdk/test/modules/tbtc/Deposit.test.ts b/sdk/test/modules/tbtc/Deposit.test.ts new file mode 100644 index 000000000..5c5558724 --- /dev/null +++ b/sdk/test/modules/tbtc/Deposit.test.ts @@ -0,0 +1,106 @@ +import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" +import TbtcApi, { DepositStatus } from "../../../src/api/TbtcApi" +import Deposit from "../../../src/modules/tbtc/Deposit" + +import { fundingUtxo, revealTestData } from "../../data/deposit" + +describe("Deposit", () => { + const tbtcApi: TbtcApi = new TbtcApi("https://api.acre.fi/v1/deposit/") + const tbtcSdkDeposit: TbtcSdkDeposit = jest.fn() as unknown as TbtcSdkDeposit + + let deposit: Deposit + + beforeAll(() => { + tbtcSdkDeposit.detectFunding = jest.fn() + + deposit = new Deposit(tbtcApi, tbtcSdkDeposit, revealTestData) + }) + + describe("waitForFunding", () => { + describe("when waiting for bitcoin deposit tx", () => { + describe("when can't find transaction after max number of retries", () => { + beforeAll(() => { + jest.useFakeTimers() + + jest + .spyOn(tbtcSdkDeposit, "detectFunding") + .mockClear() + .mockResolvedValue([]) + }) + + it("should throw an error", async () => { + // eslint-disable-next-line no-void + void expect( + deposit.waitForFunding({ + retries: 5, + backoffStepMs: 1234, + }), + ).rejects.toThrow("Deposit not funded yet") + + await jest.runAllTimersAsync() + + expect(tbtcSdkDeposit.detectFunding).toHaveBeenCalledTimes(6) + }) + }) + + describe("when the funding tx is available", () => { + let txPromise: Promise + + beforeAll(async () => { + jest.useFakeTimers() + + jest + .spyOn(tbtcSdkDeposit, "detectFunding") + .mockClear() + // First attempt. Deposit not funded yet. + .mockResolvedValueOnce([]) + // Second attempt. Deposit not funded yet. + .mockResolvedValueOnce([]) + // Third attempt. Deposit funded. + .mockResolvedValueOnce([fundingUtxo]) + + txPromise = deposit.waitForFunding({ + retries: 95, + backoffStepMs: 1234, + }) + + await jest.runAllTimersAsync() + }) + + it("should wait for deposit transaction", () => { + expect(tbtcSdkDeposit.detectFunding).toHaveBeenCalledTimes(3) + }) + + it("should resolve promise", async () => { + await txPromise + }) + }) + }) + }) + + describe("createDeposit", () => { + const mockedDepositId = "some-deposit-id" + + beforeAll(() => { + jest.spyOn(tbtcApi, "createDeposit").mockResolvedValue({ + depositId: mockedDepositId, + depositStatus: DepositStatus.Queued, + fundingOutpoint: fundingUtxo, + }) + }) + + afterAll(() => { + jest.spyOn(tbtcApi, "createDeposit").mockClear() + }) + + it("should create a deposit in tBTC API", async () => { + await deposit.createDeposit() + + expect(tbtcApi.createDeposit).toHaveBeenCalledWith({ + depositReceipt: revealTestData.revealInfo, + depositOwner: revealTestData.metadata.depositOwner, + referral: revealTestData.metadata.referral, + }) + }) + }) +}) diff --git a/sdk/test/modules/tbtc/Tbtc.test.ts b/sdk/test/modules/tbtc/Tbtc.test.ts new file mode 100644 index 000000000..39b3aa673 --- /dev/null +++ b/sdk/test/modules/tbtc/Tbtc.test.ts @@ -0,0 +1,178 @@ +import { + EthereumNetwork, + TBTC as TbtcSdk, + Deposit as TbtcSdkDeposit, +} from "@keep-network/tbtc-v2.ts" + +import { EthereumSignerCompatibleWithEthersV5 } from "../../../src" +import Deposit from "../../../src/modules/tbtc/Deposit" +import TbtcApi from "../../../src/api/TbtcApi" + +import Tbtc from "../../../src/modules/tbtc" +import { + depositTestData, + receiptTestData, + revealTestData, +} from "../../data/deposit" +import { MockAcreContracts } from "../../utils/mock-acre-contracts" + +import { MockTbtcSdk } from "../../utils/mock-tbtc-sdk" + +jest.mock("@keep-network/tbtc-v2.ts", (): object => ({ + TbtcSdk: jest.fn(), + ...jest.requireActual("@keep-network/tbtc-v2.ts"), +})) + +class MockEthereumSignerCompatibleWithEthersV5 extends EthereumSignerCompatibleWithEthersV5 { + getAddress = jest.fn() + + connect = jest.fn() + + signTransaction = jest.fn() + + signMessage = jest.fn() + + signTypedData = jest.fn() +} + +describe("Tbtc", () => { + const tbtcApiUrl = "https://api.acre.fi/v1/deposit/" + + const tbtcSdk: TbtcSdk = new MockTbtcSdk() + + const { bitcoinDepositor } = new MockAcreContracts() + + describe("initialize", () => { + const mockedSigner: EthereumSignerCompatibleWithEthersV5 = + new MockEthereumSignerCompatibleWithEthersV5() + + describe("when network is mainnet", () => { + const network: EthereumNetwork = "mainnet" + + let result: Tbtc + + beforeAll(async () => { + jest.spyOn(TbtcSdk, "initializeMainnet").mockResolvedValueOnce(tbtcSdk) + + result = await Tbtc.initialize( + mockedSigner, + network, + tbtcApiUrl, + bitcoinDepositor, + ) + }) + + it("should initialize TbtcSdk for mainnet", () => { + expect(TbtcSdk.initializeMainnet).toHaveBeenCalledWith(mockedSigner) + }) + + it("should return initialized Tbtc module", () => { + expect(result).toBeInstanceOf(Tbtc) + }) + }) + + describe("when network is sepolia", () => { + const network: EthereumNetwork = "sepolia" + + let result: Tbtc + + beforeAll(async () => { + jest.spyOn(TbtcSdk, "initializeSepolia").mockResolvedValueOnce(tbtcSdk) + + result = await Tbtc.initialize( + mockedSigner, + network, + tbtcApiUrl, + bitcoinDepositor, + ) + }) + + it("should initialize TbtcSdk for sepolia", () => { + expect(TbtcSdk.initializeSepolia).toHaveBeenCalledWith(mockedSigner) + }) + + it("should return initialized Tbtc module", () => { + expect(result).toBeInstanceOf(Tbtc) + }) + }) + }) + + describe("initiateDeposit", () => { + const tbtcApi: TbtcApi = new TbtcApi(tbtcApiUrl) + + let tbtc: Tbtc + + beforeAll(() => { + tbtc = new Tbtc(tbtcApi, tbtcSdk, bitcoinDepositor) + }) + + describe("when Bitcoin address is empty", () => { + it("should throw an error", async () => { + const emptyBitcoinRecoveryAddress = "" + + await expect( + tbtc.initiateDeposit( + depositTestData.depositOwner, + emptyBitcoinRecoveryAddress, + depositTestData.referral, + ), + ).rejects.toThrow("Ethereum or Bitcoin address is not available") + }) + }) + + describe("when Bitcoin address is provided", () => { + beforeAll(() => { + bitcoinDepositor.encodeExtraData = jest + .fn() + .mockReturnValue(depositTestData.extraData) + + const tbtcSdkDeposit: TbtcSdkDeposit = + jest.fn() as unknown as TbtcSdkDeposit + + tbtcSdkDeposit.getReceipt = jest.fn().mockReturnValue(receiptTestData) + + tbtcSdk.deposits.initiateDepositWithProxy = jest + .fn() + .mockReturnValue(tbtcSdkDeposit) + }) + + describe("when saveReveal succeeded", () => { + let result: Deposit + + beforeAll(async () => { + jest.spyOn(tbtcApi, "saveReveal").mockResolvedValueOnce(true) + + result = await tbtc.initiateDeposit( + depositTestData.depositOwner, + depositTestData.bitcoinRecoveryAddress, + depositTestData.referral, + ) + }) + + it("should call saveReveal", () => { + expect(tbtcApi.saveReveal).toHaveBeenCalledWith(revealTestData) + }) + + it("should initiate a deposit", () => { + expect(result).toBeInstanceOf(Deposit) + }) + }) + + describe("when saveReveal failed", () => { + beforeAll(() => { + jest.spyOn(tbtcApi, "saveReveal").mockResolvedValueOnce(false) + }) + + it("should throw an error", async () => { + await expect( + tbtc.initiateDeposit( + depositTestData.depositOwner, + depositTestData.bitcoinRecoveryAddress, + depositTestData.referral, + ), + ).rejects.toThrow("Reveal not saved properly in the database") + }) + }) + }) + }) +}) diff --git a/sdk/test/utils/mock-tbtc-sdk.ts b/sdk/test/utils/mock-tbtc-sdk.ts new file mode 100644 index 000000000..714a25782 --- /dev/null +++ b/sdk/test/utils/mock-tbtc-sdk.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +import { + BitcoinClient, + BitcoinNetwork, + DepositsService, + MaintenanceService, + RedemptionsService, + TBTC, + TBTCContracts, +} from "@keep-network/tbtc-v2.ts" + +// eslint-disable-next-line import/prefer-default-export +export class MockTbtcSdk implements TBTC { + deposits: DepositsService + + maintenance: MaintenanceService + + redemptions: RedemptionsService + + tbtcContracts: TBTCContracts + + bitcoinClient: BitcoinClient + + constructor() { + this.deposits = jest.fn() as unknown as DepositsService + this.maintenance = jest.fn() as unknown as MaintenanceService + this.redemptions = jest.fn() as unknown as RedemptionsService + this.tbtcContracts = jest.fn() as unknown as TBTCContracts + this.bitcoinClient = { + getNetwork: jest.fn().mockResolvedValue(BitcoinNetwork.Testnet), + } as unknown as BitcoinClient + } +} diff --git a/sdk/test/utils/mock-tbtc.ts b/sdk/test/utils/mock-tbtc.ts index 7e8192a41..5610f08cd 100644 --- a/sdk/test/utils/mock-tbtc.ts +++ b/sdk/test/utils/mock-tbtc.ts @@ -1,33 +1,17 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -import { - BitcoinClient, - BitcoinNetwork, - DepositsService, - MaintenanceService, - RedemptionsService, - TBTC, - TBTCContracts, -} from "@keep-network/tbtc-v2.ts" +import { TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" -// eslint-disable-next-line import/prefer-default-export -export class MockTBTC implements TBTC { - deposits: DepositsService - - maintenance: MaintenanceService - - redemptions: RedemptionsService +import Tbtc from "../../src/modules/tbtc" +import TbtcApi from "../../src/api/TbtcApi" - tbtcContracts: TBTCContracts - - bitcoinClient: BitcoinClient +import { BitcoinDepositor } from "../../src/lib/contracts" +// eslint-disable-next-line import/prefer-default-export +export class MockTbtc extends Tbtc { constructor() { - this.deposits = jest.fn() as unknown as DepositsService - this.maintenance = jest.fn() as unknown as MaintenanceService - this.redemptions = jest.fn() as unknown as RedemptionsService - this.tbtcContracts = jest.fn() as unknown as TBTCContracts - this.bitcoinClient = { - getNetwork: jest.fn().mockResolvedValue(BitcoinNetwork.Testnet), - } as unknown as BitcoinClient + const tbtcApi = jest.fn() as unknown as TbtcApi + const tbtcSdk = jest.fn() as unknown as TbtcSdk + const bitcoinDepositor = jest.fn() as unknown as BitcoinDepositor + + super(tbtcApi, tbtcSdk, bitcoinDepositor) } } From 7056272b323d774b6529774e978fdeba57ccbe26 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:46:12 +0200 Subject: [PATCH 17/51] Export DepositStatus enum --- sdk/src/api/TbtcApi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/api/TbtcApi.ts index 786e9f0b2..a64c3b9fa 100644 --- a/sdk/src/api/TbtcApi.ts +++ b/sdk/src/api/TbtcApi.ts @@ -99,7 +99,7 @@ type CreateDepositRequest = { /** * Represents the status of a deposit. */ -enum DepositStatus { +export enum DepositStatus { Queued = "QUEUED", Initialized = "INITIALIZED", Finalized = "FINALIZED", From 1406659333980e006837a1138819363bcf2e12e9 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:46:35 +0200 Subject: [PATCH 18/51] Update wildcard for test files overrides Some of the test files are placed in nested directiories so we need to use `**/*` wildcard to cover them. --- sdk/.eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/.eslintrc b/sdk/.eslintrc index f330305d6..2bddd4296 100644 --- a/sdk/.eslintrc +++ b/sdk/.eslintrc @@ -3,7 +3,7 @@ "extends": ["@thesis-co"], "overrides": [ { - "files": ["test/*/*.test.ts"], + "files": ["test/**/*.test.ts"], "rules": { "@typescript-eslint/unbound-method": "off" } From 5cc7326fb9021fc3848b2f4bd8f79dbb369bfcb3 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:47:46 +0200 Subject: [PATCH 19/51] Use TS confif file for jest --- sdk/{jest.config.js => jest.config.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename sdk/{jest.config.js => jest.config.ts} (83%) diff --git a/sdk/jest.config.js b/sdk/jest.config.ts similarity index 83% rename from sdk/jest.config.js rename to sdk/jest.config.ts index ab00fc628..37ce37c74 100644 --- a/sdk/jest.config.js +++ b/sdk/jest.config.ts @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: "ts-jest", testPathIgnorePatterns: ["/dist/", "/node_modules/"], } From 8645220d1fbec31ac6afc3851a5c06b9d107c1a0 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:47:56 +0200 Subject: [PATCH 20/51] Add @ethersproject/bignumber dependency We use it in unit tests to define data for tBTC SDK, which uses BigNumber. --- sdk/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/package.json b/sdk/package.json index e29b1f487..519616401 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -15,6 +15,7 @@ }, "devDependencies": { "@babel/preset-env": "^7.23.7", + "@ethersproject/bignumber": "^5.7.0", "@thesis-co/eslint-config": "github:thesis/eslint-config#7b9bc8c", "@types/jest": "^29.5.11", "@types/node": "^20.9.4", @@ -26,8 +27,8 @@ "typescript": "^5.3.2" }, "dependencies": { - "@keep-network/tbtc-v2.ts": "2.4.0-dev.3", "@acre-btc/contracts": "workspace:*", + "@keep-network/tbtc-v2.ts": "2.4.0-dev.3", "ethers": "^6.10.0" } } From d58a851ca4e08a6c3b97b76b2862f5561ca5bdb8 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 00:52:58 +0200 Subject: [PATCH 21/51] Move api/ to lib/api/ We move the directory for consistency with existing components. --- sdk/src/{ => lib}/api/HttpApi.ts | 0 sdk/src/{ => lib}/api/TbtcApi.ts | 0 sdk/src/modules/tbtc/Deposit.ts | 4 ++-- sdk/src/modules/tbtc/Tbtc.ts | 2 +- sdk/test/data/deposit.ts | 2 +- sdk/test/modules/tbtc/Deposit.test.ts | 2 +- sdk/test/modules/tbtc/Tbtc.test.ts | 2 +- sdk/test/utils/mock-tbtc.ts | 2 +- 8 files changed, 7 insertions(+), 7 deletions(-) rename sdk/src/{ => lib}/api/HttpApi.ts (100%) rename sdk/src/{ => lib}/api/TbtcApi.ts (100%) diff --git a/sdk/src/api/HttpApi.ts b/sdk/src/lib/api/HttpApi.ts similarity index 100% rename from sdk/src/api/HttpApi.ts rename to sdk/src/lib/api/HttpApi.ts diff --git a/sdk/src/api/TbtcApi.ts b/sdk/src/lib/api/TbtcApi.ts similarity index 100% rename from sdk/src/api/TbtcApi.ts rename to sdk/src/lib/api/TbtcApi.ts diff --git a/sdk/src/modules/tbtc/Deposit.ts b/sdk/src/modules/tbtc/Deposit.ts index dd3011eed..a719b25bb 100644 --- a/sdk/src/modules/tbtc/Deposit.ts +++ b/sdk/src/modules/tbtc/Deposit.ts @@ -1,9 +1,9 @@ import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" import type { DepositReceipt as TbtcSdkDepositReceipt } from "@keep-network/tbtc-v2.ts" -import type { SaveRevealRequest as DepositRevealData } from "../../api/TbtcApi" +import type { SaveRevealRequest as DepositRevealData } from "../../lib/api/TbtcApi" -import TbtcApi from "../../api/TbtcApi" +import TbtcApi from "../../lib/api/TbtcApi" import { backoffRetrier, RetryOptions } from "../../lib/utils" diff --git a/sdk/src/modules/tbtc/Tbtc.ts b/sdk/src/modules/tbtc/Tbtc.ts index 007aa74e0..ddafdcc94 100644 --- a/sdk/src/modules/tbtc/Tbtc.ts +++ b/sdk/src/modules/tbtc/Tbtc.ts @@ -1,6 +1,6 @@ import { ChainIdentifier, TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" -import TbtcApi from "../../api/TbtcApi" +import TbtcApi from "../../lib/api/TbtcApi" import { BitcoinDepositor } from "../../lib/contracts" import { EthereumNetwork } from "../../lib/ethereum" diff --git a/sdk/test/data/deposit.ts b/sdk/test/data/deposit.ts index 42905923b..adf38d735 100644 --- a/sdk/test/data/deposit.ts +++ b/sdk/test/data/deposit.ts @@ -3,7 +3,7 @@ import { BigNumber } from "@ethersproject/bignumber" import type { ChainIdentifier } from "../../src/lib/contracts" import type { DepositReceipt } from "../../src/modules/tbtc" -import type { SaveRevealRequest } from "../../src/api/TbtcApi" +import type { SaveRevealRequest } from "../lib/api/TbtcApi" const depositTestData: { depositOwner: ChainIdentifier diff --git a/sdk/test/modules/tbtc/Deposit.test.ts b/sdk/test/modules/tbtc/Deposit.test.ts index 5c5558724..5e6e48937 100644 --- a/sdk/test/modules/tbtc/Deposit.test.ts +++ b/sdk/test/modules/tbtc/Deposit.test.ts @@ -1,5 +1,5 @@ import { Deposit as TbtcSdkDeposit } from "@keep-network/tbtc-v2.ts" -import TbtcApi, { DepositStatus } from "../../../src/api/TbtcApi" +import TbtcApi, { DepositStatus } from "../../../src/lib/api/TbtcApi" import Deposit from "../../../src/modules/tbtc/Deposit" import { fundingUtxo, revealTestData } from "../../data/deposit" diff --git a/sdk/test/modules/tbtc/Tbtc.test.ts b/sdk/test/modules/tbtc/Tbtc.test.ts index 39b3aa673..043ddec7c 100644 --- a/sdk/test/modules/tbtc/Tbtc.test.ts +++ b/sdk/test/modules/tbtc/Tbtc.test.ts @@ -6,7 +6,7 @@ import { import { EthereumSignerCompatibleWithEthersV5 } from "../../../src" import Deposit from "../../../src/modules/tbtc/Deposit" -import TbtcApi from "../../../src/api/TbtcApi" +import TbtcApi from "../../../src/lib/api/TbtcApi" import Tbtc from "../../../src/modules/tbtc" import { diff --git a/sdk/test/utils/mock-tbtc.ts b/sdk/test/utils/mock-tbtc.ts index 5610f08cd..aee034ac0 100644 --- a/sdk/test/utils/mock-tbtc.ts +++ b/sdk/test/utils/mock-tbtc.ts @@ -1,7 +1,7 @@ import { TBTC as TbtcSdk } from "@keep-network/tbtc-v2.ts" import Tbtc from "../../src/modules/tbtc" -import TbtcApi from "../../src/api/TbtcApi" +import TbtcApi from "../../src/lib/api/TbtcApi" import { BitcoinDepositor } from "../../src/lib/contracts" From f0a9e903a0d240acbffc69b3447c3f03220cf81b Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 08:11:31 +0200 Subject: [PATCH 22/51] Update pnpm-lock.yaml --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da6eba6e3..dca0a101e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,9 @@ importers: '@babel/preset-env': specifier: ^7.23.7 version: 7.23.7(@babel/core@7.23.3) + '@ethersproject/bignumber': + specifier: ^5.7.0 + version: 5.7.0 '@thesis-co/eslint-config': specifier: github:thesis/eslint-config#7b9bc8c version: github.com/thesis/eslint-config/7b9bc8c(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2) From 0bb9df8139bfbae1d7b11a40ec40070923384afc Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 12:37:05 +0200 Subject: [PATCH 23/51] Expect tBTC API URL to be provided on initialization To avoid hardcoding URL to the api in SDK source code we expect it to be provided from the dapp on initialization. --- sdk/src/acre.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sdk/src/acre.ts b/sdk/src/acre.ts index 912364816..dce8d8226 100644 --- a/sdk/src/acre.ts +++ b/sdk/src/acre.ts @@ -9,8 +9,6 @@ import { StakingModule } from "./modules/staking" import Tbtc from "./modules/tbtc" import { EthereumSignerCompatibleWithEthersV5 } from "./lib/utils" -const TBTC_API_ENDPOINT = process.env.TBTC_API_ENDPOINT ?? "" - class Acre { readonly #tbtc: Tbtc @@ -38,6 +36,7 @@ class Acre { static async initializeEthereum( signer: EthereumSignerCompatibleWithEthersV5, network: EthereumNetwork, + tbtcApiUrl: string, ): Promise { const contracts = getEthereumContracts(signer, network) const messages = new EthereumEIP712Signer(signer) @@ -45,7 +44,7 @@ class Acre { const tbtc = await Tbtc.initialize( signer, network, - TBTC_API_ENDPOINT, + tbtcApiUrl, contracts.bitcoinDepositor, ) From 6d2f82aad1f4f2b195106252c697c4d78e4907c4 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 12:38:22 +0200 Subject: [PATCH 24/51] Export DepositReceipt type The types is required by the dApp to handle the receipt. --- sdk/src/modules/staking/index.ts | 2 ++ sdk/src/modules/staking/stake-initialization.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 3fa3e4375..1892e6744 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -5,6 +5,8 @@ import { StakeInitialization } from "./stake-initialization" import { fromSatoshi, toSatoshi } from "../../lib/utils" import Tbtc from "../tbtc" +export { DepositReceipt } from "../tbtc" + /** * Represents all total deposit fees grouped by network. */ diff --git a/sdk/src/modules/staking/stake-initialization.ts b/sdk/src/modules/staking/stake-initialization.ts index 12d3406e1..722067179 100644 --- a/sdk/src/modules/staking/stake-initialization.ts +++ b/sdk/src/modules/staking/stake-initialization.ts @@ -1,6 +1,6 @@ import TbtcDeposit from "../tbtc/Deposit" -import type { DepositReceipt } from "../tbtc" +import type { DepositReceipt } from "." import { ChainEIP712Signer, From ac7685a3cb118136ef273dd649b417047d42be96 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 12:44:50 +0200 Subject: [PATCH 25/51] Pass TBTC_API_ENDPOINT to SDK initialization SDK expects tBTC API URL to be provided for initialization. We expect it ot be defined in the dApp as env variable. --- dapp/src/acre-react/contexts/AcreSdkContext.tsx | 3 +++ dapp/src/vite-env.d.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/dapp/src/acre-react/contexts/AcreSdkContext.tsx b/dapp/src/acre-react/contexts/AcreSdkContext.tsx index 6be8694a6..75dd77a60 100644 --- a/dapp/src/acre-react/contexts/AcreSdkContext.tsx +++ b/dapp/src/acre-react/contexts/AcreSdkContext.tsx @@ -2,6 +2,8 @@ import React, { useCallback, useMemo, useState } from "react" import { LedgerLiveEthereumSigner } from "#/web3" import { Acre, EthereumNetwork } from "@acre-btc/sdk" +const TBTC_API_ENDPOINT = import.meta.env.VITE_TBTC_API_ENDPOINT + type AcreSdkContextValue = { acre?: Acre init: (ethereumAddress: string, network: EthereumNetwork) => Promise @@ -25,6 +27,7 @@ export function AcreSdkProvider({ children }: { children: React.ReactNode }) { const sdk = await Acre.initializeEthereum( await LedgerLiveEthereumSigner.fromAddress(ethereumAddress), network, + TBTC_API_ENDPOINT, ) setAcre(sdk) setIsInitialized(true) diff --git a/dapp/src/vite-env.d.ts b/dapp/src/vite-env.d.ts index f127eea1e..269569e7e 100644 --- a/dapp/src/vite-env.d.ts +++ b/dapp/src/vite-env.d.ts @@ -5,6 +5,7 @@ interface ImportMetaEnv { readonly VITE_ETH_HOSTNAME_HTTP: string readonly VITE_REFERRAL: number readonly VITE_DEFENDER_RELAYER_WEBHOOK_URL: string + readonly VITE_TBTC_API_ENDPOINT: string } interface ImportMeta { From 4a132b1ebb0f405335d7b23d8396fc416e296cbf Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 30 Apr 2024 13:52:40 +0200 Subject: [PATCH 26/51] Remove custom depositor with defender relayer implementation We don't need to implement bitcoin depositor contract implementation using the defender relayer as we use tbtc api to relay the deposit creation requests. --- dapp/src/acre-react/hooks/useStakeFlow.ts | 4 - dapp/src/contexts/StakeFlowContext.tsx | 11 +-- dapp/src/vite-env.d.ts | 1 - dapp/src/web3/index.ts | 1 - dapp/src/web3/relayer-depositor-proxy.ts | 96 ----------------------- 5 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 dapp/src/web3/relayer-depositor-proxy.ts diff --git a/dapp/src/acre-react/hooks/useStakeFlow.ts b/dapp/src/acre-react/hooks/useStakeFlow.ts index b13c3b8a5..7b301ee4a 100644 --- a/dapp/src/acre-react/hooks/useStakeFlow.ts +++ b/dapp/src/acre-react/hooks/useStakeFlow.ts @@ -2,7 +2,6 @@ import { useCallback, useState } from "react" import { StakeInitialization, EthereumAddress, - DepositorProxy, DepositReceipt, } from "@acre-btc/sdk" import { useAcreContext } from "./useAcreContext" @@ -12,7 +11,6 @@ export type UseStakeFlowReturn = { bitcoinRecoveryAddress: string, ethereumAddress: string, referral: number, - depositor?: DepositorProxy, ) => Promise btcAddress?: string depositReceipt?: DepositReceipt @@ -36,7 +34,6 @@ export function useStakeFlow(): UseStakeFlowReturn { bitcoinRecoveryAddress: string, ethereumAddress: string, referral: number, - depositor?: DepositorProxy, ) => { if (!acre || !isInitialized) throw new Error("Acre SDK not defined") @@ -44,7 +41,6 @@ export function useStakeFlow(): UseStakeFlowReturn { bitcoinRecoveryAddress, EthereumAddress.from(ethereumAddress), referral, - depositor, ) const btcDepositAddress = await initializedStakeFlow.getBitcoinAddress() diff --git a/dapp/src/contexts/StakeFlowContext.tsx b/dapp/src/contexts/StakeFlowContext.tsx index d19cad509..adadd561c 100644 --- a/dapp/src/contexts/StakeFlowContext.tsx +++ b/dapp/src/contexts/StakeFlowContext.tsx @@ -5,8 +5,6 @@ import { useStakeFlow, } from "#/acre-react/hooks" import { REFERRAL } from "#/constants" -import { RelayerDepositorProxy } from "#/web3" -import { EthereumBitcoinDepositor } from "@acre-btc/sdk" type StakeFlowContextValue = Omit & { initStake: ( @@ -35,14 +33,7 @@ export function StakeFlowProvider({ children }: { children: React.ReactNode }) { async (bitcoinRecoveryAddress: string, ethereumAddress: string) => { if (!acre) throw new Error("Acre SDK not defined") - await acreInitStake( - bitcoinRecoveryAddress, - ethereumAddress, - REFERRAL, - RelayerDepositorProxy.fromEthereumBitcoinDepositor( - acre.contracts.bitcoinDepositor as EthereumBitcoinDepositor, - ), - ) + await acreInitStake(bitcoinRecoveryAddress, ethereumAddress, REFERRAL) }, [acreInitStake, acre], ) diff --git a/dapp/src/vite-env.d.ts b/dapp/src/vite-env.d.ts index 269569e7e..3f78f074c 100644 --- a/dapp/src/vite-env.d.ts +++ b/dapp/src/vite-env.d.ts @@ -4,7 +4,6 @@ interface ImportMetaEnv { readonly VITE_SENTRY_DSN: string readonly VITE_ETH_HOSTNAME_HTTP: string readonly VITE_REFERRAL: number - readonly VITE_DEFENDER_RELAYER_WEBHOOK_URL: string readonly VITE_TBTC_API_ENDPOINT: string } diff --git a/dapp/src/web3/index.ts b/dapp/src/web3/index.ts index 3d4e2a169..f38e7e840 100644 --- a/dapp/src/web3/index.ts +++ b/dapp/src/web3/index.ts @@ -1,2 +1 @@ export * from "./ledger-live-signer" -export * from "./relayer-depositor-proxy" diff --git a/dapp/src/web3/relayer-depositor-proxy.ts b/dapp/src/web3/relayer-depositor-proxy.ts deleted file mode 100644 index 7207df6f9..000000000 --- a/dapp/src/web3/relayer-depositor-proxy.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { - BitcoinRawTxVectors, - DepositorProxy, - EthereumBitcoinDepositor, - ChainIdentifier, - DepositReceipt, - Hex, - packRevealDepositParameters, - BitcoinDepositor, -} from "@acre-btc/sdk" -import axios from "axios" - -const DEFENDER_WEBHOOK_URL = import.meta.env.VITE_DEFENDER_RELAYER_WEBHOOK_URL - -/** - * Implementation of @see DepositorProxy that redirects stake requests to the - * Open Zeppelin Defender Relayer which initializes stake on the user's behalf. - * Sends the HTTP POST request to the webhook at the provided URL with the data - * necessary to initialize stake. - */ -class RelayerDepositorProxy - implements DepositorProxy -{ - /** - * Chain-specific handle to @see BitcoinDepositor contract. - */ - #bitcoinDepositor: T - - /** - * Defines the Open Zeppelin Defender webhook URL. - */ - #defenderWebhookUrl: string - - /** - * Creates the instance of the relayer depositor proxy for Ethereum chain. - * @param bitcoinDepositor Ethereum handle to @see BitcoinDepositor contract. - * @returns Instance of @see RelayerDepositorProxy. - */ - static fromEthereumBitcoinDepositor( - bitcoinDepositor: EthereumBitcoinDepositor, - ): RelayerDepositorProxy { - return new RelayerDepositorProxy(bitcoinDepositor, DEFENDER_WEBHOOK_URL) - } - - private constructor(_bitcoinDepositor: T, _defenderWebhookUr: string) { - this.#bitcoinDepositor = _bitcoinDepositor - this.#defenderWebhookUrl = _defenderWebhookUr - } - - /** - * @see {DepositorProxy#getChainIdentifier} - */ - getChainIdentifier(): ChainIdentifier { - return this.#bitcoinDepositor.getChainIdentifier() - } - - /** - * @see {DepositorProxy#revealDeposit} - * @dev Sends HTTP POST request to Open Zeppelin Defender Relayer. - */ - async revealDeposit( - depositTx: BitcoinRawTxVectors, - depositOutputIndex: number, - deposit: DepositReceipt, - ): Promise { - const { fundingTx, reveal, extraData } = packRevealDepositParameters( - depositTx, - depositOutputIndex, - deposit, - await this.#bitcoinDepositor.getTbtcVaultChainIdentifier(), - ) - - if (!extraData) throw new Error("Invalid extra data") - - const { depositOwner, referral } = - this.#bitcoinDepositor.decodeExtraData(extraData) - - // TODO: Catch and handle errors + sentry. - const response = await axios.post<{ result: string }>( - this.#defenderWebhookUrl, - { - fundingTx, - reveal, - depositOwner: `0x${depositOwner.identifierHex}`, - referral, - }, - ) - - // Defender returns result as string so we need to parse it. - const { txHash } = JSON.parse(response.data.result) as { txHash: string } - - return Hex.from(txHash) - } -} - -export { RelayerDepositorProxy } From b4f6d940840e69d3583ad0d0ea51e629e3a14d2e Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Wed, 1 May 2024 17:24:51 +0200 Subject: [PATCH 27/51] Save the funding Bitcoin tx id in subgraph Find the `DepositRevealed` event in transaction logs and save the Bitcoin transaction id in subgraph. The Bitcoin transaction id is stored in the same byte order as used by the Bitcoin block explorers to create links to the Bitcoin explorers easily. --- subgraph/src/bitcoin-depositor.ts | 7 +- subgraph/src/tbtc-utils.ts | 59 ++++++++++++++++ subgraph/tests/bitcoin-depositor-utils.ts | 43 +++++++++++- subgraph/tests/bitcoin-depositor.test.ts | 86 ++++++++++++++--------- 4 files changed, 156 insertions(+), 39 deletions(-) create mode 100644 subgraph/src/tbtc-utils.ts diff --git a/subgraph/src/bitcoin-depositor.ts b/subgraph/src/bitcoin-depositor.ts index 957af9a2e..98849dc4e 100644 --- a/subgraph/src/bitcoin-depositor.ts +++ b/subgraph/src/bitcoin-depositor.ts @@ -8,6 +8,7 @@ import { getOrCreateDeposit, getOrCreateEvent, } from "./utils" +import { findBitcoinTransactionIdFromTransactionReceipt } from "./tbtc-utils" export function handleDepositInitialized(event: DepositInitializedEvent): void { const depositOwnerEntity = getOrCreateDepositOwner(event.params.depositOwner) @@ -15,12 +16,12 @@ export function handleDepositInitialized(event: DepositInitializedEvent): void { event.params.depositKey.toHexString(), ) - // TODO: Get the bitcoin transaction hash from this Ethereum transaction - // by finding the `DepositRevealed` event in logs from the tBTC-v2 Bridge - // contract. depositEntity.depositOwner = depositOwnerEntity.id depositEntity.initialDepositAmount = event.params.initialAmount + depositEntity.bitcoinTransactionId = + findBitcoinTransactionIdFromTransactionReceipt(event.receipt) + const eventEntity = getOrCreateEvent( `${event.transaction.hash.toHexString()}_DepositInitialized`, ) diff --git a/subgraph/src/tbtc-utils.ts b/subgraph/src/tbtc-utils.ts new file mode 100644 index 000000000..d6610cfa8 --- /dev/null +++ b/subgraph/src/tbtc-utils.ts @@ -0,0 +1,59 @@ +import { + Address, + ethereum, + crypto, + ByteArray, + BigInt, + Bytes, +} from "@graphprotocol/graph-ts" + +const DEPOSIT_REVEALED_EVENT_SIGNATURE = crypto.keccak256( + ByteArray.fromUTF8( + "DepositRevealed(bytes32,uint32,address,uint64,bytes8,bytes20,bytes20,bytes4,address)", + ), +) + +// TODO: Set correct address for mainnet. +const TBTC_V2_BRIDGE_CONTRACT_ADDRESS = Address.fromString( + "0x9b1a7fE5a16A15F2f9475C5B231750598b113403", +) + +// eslint-disable-next-line import/prefer-default-export +export function findBitcoinTransactionIdFromTransactionReceipt( + transactionReceipt: ethereum.TransactionReceipt | null, +): string { + if (!transactionReceipt) { + throw new Error("Transaction receipt not available") + } + + // We must cast manually to `ethereum.TransactionReceipt` otherwise + // AssemblyScript will fail. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + const receipt = transactionReceipt as ethereum.TransactionReceipt + + const depositRevealedLogIndex = receipt.logs.findIndex( + (receiptLog) => + receiptLog.address.equals(TBTC_V2_BRIDGE_CONTRACT_ADDRESS) && + receiptLog.topics[0].equals(DEPOSIT_REVEALED_EVENT_SIGNATURE), + ) + + if (depositRevealedLogIndex < 0) { + throw new Error("Cannot find `DepositRevealed` event in transaction logs") + } + + const depositRevealedLog = receipt.logs[depositRevealedLogIndex] + + // Bitcoin transaction hash in little-endian byte order. The first 32 bytes + // (w/o `0x` prefix) points to the Bitcoin transaction hash. + const bitcoinTxHash = depositRevealedLog.data.toHexString().slice(2, 66) + + // Bitcoin transaction id in the same byte order as used by the + // Bitcoin block explorers. + const bitcoinTransactionId = BigInt.fromUnsignedBytes( + Bytes.fromHexString(bitcoinTxHash), + ) + .toHexString() + .slice(2) + + return bitcoinTransactionId +} diff --git a/subgraph/tests/bitcoin-depositor-utils.ts b/subgraph/tests/bitcoin-depositor-utils.ts index cb809517c..cf1099b1f 100644 --- a/subgraph/tests/bitcoin-depositor-utils.ts +++ b/subgraph/tests/bitcoin-depositor-utils.ts @@ -1,4 +1,10 @@ -import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts" +import { + ethereum, + BigInt, + Address, + Bytes, + Wrapped, +} from "@graphprotocol/graph-ts" import { newMockEvent } from "matchstick-as/assembly/defaults" import { DepositInitialized, @@ -10,6 +16,7 @@ export function createDepositInitializedEvent( caller: Address, depositOwner: Address, initialAmount: BigInt, + btcFundingTxHash: string, ): DepositInitialized { const depositInitializedEvent = changetype(newMockEvent()) @@ -34,6 +41,40 @@ export function createDepositInitializedEvent( ethereum.Value.fromUnsignedBigInt(initialAmount), ) + // Logs data from https://sepolia.etherscan.io/tx/0x6805986942c86496853cb1d0146120a6b55e57fb4feec605c49edef2b34903bb#eventlog + const log = new ethereum.Log( + Address.fromString("0x9b1a7fE5a16A15F2f9475C5B231750598b113403"), + [ + // Event signature + Bytes.fromHexString( + "0xa7382159a693ed317a024daf0fd1ba30805cdf9928ee09550af517c516e2ef05", + ), + // `depositor` - indexed topic 1 + Bytes.fromHexString( + "0x0000000000000000000000002f86fe8c5683372db667e6f6d88dcb6d55a81286", + ), + // `walletPubKeyHash` - indexed topic 2 + Bytes.fromHexString( + "0x79073502d1fcf0cc9b9a1b7c56cadda76d33fe98000000000000000000000000", + ), + ], + Bytes.fromHexString( + `0x${btcFundingTxHash}000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000186a00f56f5715acb9a25000000000000000000000000000000000000000000000000f2096fc9cbf2aca10025af13cbf9a685d963fde8000000000000000000000000e1c1946700000000000000000000000000000000000000000000000000000000000000000000000000000000b5679de944a79732a75ce556191df11f489448d5`, + ), + depositInitializedEvent.block.hash, + Bytes.fromI32(1), + depositInitializedEvent.transaction.hash, + depositInitializedEvent.transaction.index, + depositInitializedEvent.logIndex, + depositInitializedEvent.transactionLogIndex, + depositInitializedEvent.logType as string, + new Wrapped(false), + ) + + ;(depositInitializedEvent.receipt as ethereum.TransactionReceipt).logs.push( + log, + ) + depositInitializedEvent.parameters.push(depositKeyParam) depositInitializedEvent.parameters.push(callerParam) depositInitializedEvent.parameters.push(depositOwnerParam) diff --git a/subgraph/tests/bitcoin-depositor.test.ts b/subgraph/tests/bitcoin-depositor.test.ts index b9ac5d333..4fc485dd3 100644 --- a/subgraph/tests/bitcoin-depositor.test.ts +++ b/subgraph/tests/bitcoin-depositor.test.ts @@ -5,8 +5,6 @@ import { clearStore, beforeAll, afterAll, - beforeEach, - afterEach, } from "matchstick-as/assembly/index" import { BigInt, Address } from "@graphprotocol/graph-ts" @@ -18,9 +16,9 @@ import { handleDepositFinalized, handleDepositInitialized, } from "../src/bitcoin-depositor" +import { DepositOwner } from "../generated/schema" -const depositKey = BigInt.fromI32(234) -const nextDepositKey = BigInt.fromI32(345) +// Shared const caller = Address.fromString("0x0000000000000000000000000000000000000001") const depositOwner = Address.fromString( "0x0000000000000000000000000000000000000001", @@ -30,20 +28,35 @@ const initialAmount = BigInt.fromI32(234) const bridgedAmount = BigInt.fromI32(234) const depositorFee = BigInt.fromI32(234) +// First deposit +const depositKey = BigInt.fromI32(234) +const fundingTxHash = + "03063F39C8F0F9D3D742A73D1D5AF22548BFFF2E4D292BEAFF2BE1FE75CE1556" +const expectedBitcoinTxId = + "5615CE75FEE12BFFEA2B294D2EFFBF4825F25A1D3DA742D7D3F9F0C8393F0603".toLowerCase() const depositInitializedEvent = createDepositInitializedEvent( depositKey, caller, depositOwner, initialAmount, + fundingTxHash, ) -const nextDepositInitializedEvent = createDepositInitializedEvent( - nextDepositKey, +// Second deposit +const secondDepositKey = BigInt.fromI32(555) +const secondFundingTxHash = + "1F0BCD97CD8556AFBBF0EE1D319A8F873B11EA60C536F57AAF25812B19A7F76C" +const secondExpectedBitcoinTxId = + "6CF7A7192B8125AF7AF536C560EA113B878F9A311DEEF0BBAF5685CD97CD0B1F".toLowerCase() +const secondDepositInitializedEvent = createDepositInitializedEvent( + secondDepositKey, caller, depositOwner, initialAmount, + secondFundingTxHash, ) +// First deposit finalized const depositFinalizedEvent = createDepositFinalizedEvent( depositKey, caller, @@ -76,19 +89,29 @@ describe("handleDepositInitialized", () => { }) test("Deposit entity has proper fields", () => { + const depositEntityId = + depositInitializedEvent.params.depositKey.toHexString() + assert.fieldEquals( "Deposit", - depositInitializedEvent.params.depositKey.toHexString(), + depositEntityId, "depositOwner", depositOwner.toHexString(), ) assert.fieldEquals( "Deposit", - depositInitializedEvent.params.depositKey.toHexString(), + depositEntityId, "initialDepositAmount", depositInitializedEvent.params.initialAmount.toString(), ) + + assert.fieldEquals( + "Deposit", + depositEntityId, + "bitcoinTransactionId", + expectedBitcoinTxId, + ) }) test("Event entity has proper fields", () => { @@ -115,70 +138,63 @@ describe("handleDepositInitialized", () => { describe("when the deposit owner already exists", () => { beforeAll(() => { handleDepositInitialized(depositInitializedEvent) - handleDepositInitialized(nextDepositInitializedEvent) + handleDepositInitialized(secondDepositInitializedEvent) }) afterAll(() => { clearStore() }) - test("should create DepositOwner entity", () => { - assert.entityCount("DepositOwner", 1) - }) + test("the DepositOwner entity should already exists", () => { + const existingDepositOwner = DepositOwner.load( + secondDepositInitializedEvent.params.depositOwner.toHexString(), + ) - test("should create Deposit entity", () => { - assert.entityCount("Deposit", 2) + assert.assertNotNull(existingDepositOwner) + assert.entityCount("DepositOwner", 1) }) - test("should create Event entity", () => { - assert.entityCount("Event", 1) - }) + test("should create the second deposit correctly", () => { + const secondDepositEntityId = + secondDepositInitializedEvent.params.depositKey.toHexString() - test("Deposit entity has proper fields", () => { assert.fieldEquals( "Deposit", - depositInitializedEvent.params.depositKey.toHexString(), + secondDepositEntityId, "depositOwner", depositOwner.toHexString(), ) assert.fieldEquals( "Deposit", - depositInitializedEvent.params.depositKey.toHexString(), + secondDepositEntityId, "initialDepositAmount", depositInitializedEvent.params.initialAmount.toString(), ) assert.fieldEquals( "Deposit", - nextDepositInitializedEvent.params.depositKey.toHexString(), - "depositOwner", - depositOwner.toHexString(), - ) - - assert.fieldEquals( - "Deposit", - nextDepositInitializedEvent.params.depositKey.toHexString(), - "initialDepositAmount", - nextDepositInitializedEvent.params.initialAmount.toString(), + secondDepositEntityId, + "bitcoinTransactionId", + secondExpectedBitcoinTxId, ) }) test("Event entity has proper fields", () => { - const nextTxId = `${nextDepositInitializedEvent.transaction.hash.toHexString()}_DepositInitialized` + const nextTxId = `${secondDepositInitializedEvent.transaction.hash.toHexString()}_DepositInitialized` assert.fieldEquals( "Event", nextTxId, "activity", - nextDepositInitializedEvent.params.depositKey.toHexString(), + secondDepositInitializedEvent.params.depositKey.toHexString(), ) assert.fieldEquals( "Event", nextTxId, "timestamp", - nextDepositInitializedEvent.block.timestamp.toString(), + secondDepositInitializedEvent.block.timestamp.toString(), ) assert.fieldEquals("Event", nextTxId, "type", "Initialized") @@ -188,12 +204,12 @@ describe("handleDepositInitialized", () => { describe("handleDepositFinalized", () => { describe("when deposit entity already exist", () => { - beforeEach(() => { + beforeAll(() => { handleDepositInitialized(depositInitializedEvent) handleDepositFinalized(depositFinalizedEvent) }) - afterEach(() => { + afterAll(() => { clearStore() }) From 3187152a5c1c9f9a3664ba67aa9648db4bd7a33c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 2 May 2024 13:02:24 +0200 Subject: [PATCH 28/51] Bump graph dependencies to the latest version The types in unit tests were incompatible so we bumped the `graph-cli`, `graph-ts` and `matchstick-as` dependencies to the latest versions. --- pnpm-lock.yaml | 38 ++++++++++++++++++-------------------- subgraph/package.json | 6 +++--- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da6eba6e3..1beeb0fc4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,11 +294,11 @@ importers: subgraph: dependencies: '@graphprotocol/graph-cli': - specifier: 0.68.3 - version: 0.68.3(@types/node@20.9.4)(node-fetch@2.7.0)(typescript@5.3.2) + specifier: 0.71.0 + version: 0.71.0(@types/node@20.9.4)(node-fetch@2.7.0)(typescript@5.3.2) '@graphprotocol/graph-ts': - specifier: 0.32.0 - version: 0.32.0 + specifier: 0.35.1 + version: 0.35.1 assemblyscript: specifier: 0.19.23 version: 0.19.23 @@ -316,8 +316,8 @@ importers: specifier: ^8.54.0 version: 8.54.0 matchstick-as: - specifier: 0.5.0 - version: 0.5.0 + specifier: 0.6.0 + version: 0.6.0 prettier: specifier: ^3.1.0 version: 3.1.0 @@ -3882,8 +3882,8 @@ packages: html-entities: 2.4.0 strip-ansi: 6.0.1 - /@graphprotocol/graph-cli@0.68.3(@types/node@20.9.4)(node-fetch@2.7.0)(typescript@5.3.2): - resolution: {integrity: sha512-0WMiS7DpxanADLhcj8mhfugx5r4WL8RL46eQ35GcaSfr3H61Pz5RtusEayxfrhtXnzNcFcbnE4fr5TrR9R0iaQ==} + /@graphprotocol/graph-cli@0.71.0(@types/node@20.9.4)(node-fetch@2.7.0)(typescript@5.3.2): + resolution: {integrity: sha512-ITcSBHuXPuaoRs7FzNtqD0tCOIy4JGsM3j4IQNA2yZgXgr/TmmHG7KTB/c3B5Zlnsr9foXrU71T6ixGmwJ4PKw==} engines: {node: '>=18'} hasBin: true dependencies: @@ -3926,14 +3926,8 @@ packages: - utf-8-validate dev: false - /@graphprotocol/graph-ts@0.27.0: - resolution: {integrity: sha512-r1SPDIZVQiGMxcY8rhFSM0y7d/xAbQf5vHMWUf59js1KgoyWpM6P3tczZqmQd7JTmeyNsDGIPzd9FeaxllsU4w==} - dependencies: - assemblyscript: 0.19.10 - dev: true - - /@graphprotocol/graph-ts@0.32.0: - resolution: {integrity: sha512-YfKLT2w+ItXD/VPYQiAKtINQONVsAOkcqVFMHlhUy0fcEBVWuFBT53hJNI0/l5ujQa4TSxtzrKW/7EABAdgI8g==} + /@graphprotocol/graph-ts@0.35.1: + resolution: {integrity: sha512-74CfuQmf7JI76/XCC34FTkMMKeaf+3Pn0FIV3m9KNeaOJ+OI3CvjMIVRhOZdKcJxsFCBGaCCl0eQjh47xTjxKA==} dependencies: assemblyscript: 0.19.10 dev: false @@ -8207,6 +8201,7 @@ packages: dependencies: binaryen: 101.0.0-nightly.20210723 long: 4.0.0 + dev: false /assemblyscript@0.19.23: resolution: {integrity: sha512-fwOQNZVTMga5KRsfY80g7cpOl4PsFQczMwHzdtgoqLXaYhkhavufKb0sB0l3T1DUxpAufA0KNhlbpuuhZUwxMA==} @@ -8215,6 +8210,7 @@ packages: binaryen: 102.0.0-nightly.20211028 long: 5.2.3 source-map-support: 0.5.21 + dev: false /assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} @@ -8701,10 +8697,12 @@ packages: /binaryen@101.0.0-nightly.20210723: resolution: {integrity: sha512-eioJNqhHlkguVSbblHOtLqlhtC882SOEPKmNFZaDuz1hzQjolxZ+eu3/kaS10n3sGPONsIZsO7R9fR00UyhEUA==} hasBin: true + dev: false /binaryen@102.0.0-nightly.20211028: resolution: {integrity: sha512-GCJBVB5exbxzzvyt8MGDv/MeUjs6gkXDvf4xOIItRBptYl0Tz5sm1o/uG95YK0L0VeG5ajDu3hRtkBP2kzqC5w==} hasBin: true + dev: false /bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -15664,9 +15662,11 @@ packages: /long@4.0.0: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + dev: false /long@5.2.3: resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} @@ -15792,11 +15792,9 @@ packages: resolution: {integrity: sha512-0EESkXiTkWzrQQntBu2uzKvLu6vVkUGz40nGPbSZuegcfE5UuSzNjLaIu76zJWuaT/2I3Z/8M06OlUOZLGwLlQ==} dev: true - /matchstick-as@0.5.0: - resolution: {integrity: sha512-4K619YDH+so129qt4RB4JCNxaFwJJYLXPc7drpG+/mIj86Cfzg6FKs/bA91cnajmS1CLHdhHl9vt6Kd6Oqvfkg==} + /matchstick-as@0.6.0: + resolution: {integrity: sha512-E36fWsC1AbCkBFt05VsDDRoFvGSdcZg6oZJrtIe/YDBbuFh8SKbR5FcoqDhNWqSN+F7bN/iS2u8Md0SM+4pUpw==} dependencies: - '@graphprotocol/graph-ts': 0.27.0 - assemblyscript: 0.19.23 wabt: 1.0.24 dev: true diff --git a/subgraph/package.json b/subgraph/package.json index 42bcca0f4..3a2b00094 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -18,8 +18,8 @@ "lint:config:fix": "prettier -w '**/*.@(json|yaml|toml)'" }, "dependencies": { - "@graphprotocol/graph-cli": "0.68.3", - "@graphprotocol/graph-ts": "0.32.0", + "@graphprotocol/graph-cli": "0.71.0", + "@graphprotocol/graph-ts": "0.35.1", "assemblyscript": "0.19.23" }, "devDependencies": { @@ -27,7 +27,7 @@ "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "eslint": "^8.54.0", - "matchstick-as": "0.5.0", + "matchstick-as": "0.6.0", "prettier": "^3.1.0" } } From 8b1888bfc0fe2247ca98b231464cbd65de028783 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 2 May 2024 13:04:59 +0200 Subject: [PATCH 29/51] Store the tBTC Bridge address in context Instead of hardcoding the value in the code we can pass it via subgraph manifest. --- subgraph/src/tbtc-utils.ts | 26 +++++++++++++++--------- subgraph/subgraph.yaml | 4 ++++ subgraph/tests/bitcoin-depositor.test.ts | 21 ++++++++++++++++++- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/subgraph/src/tbtc-utils.ts b/subgraph/src/tbtc-utils.ts index d6610cfa8..cc03bd155 100644 --- a/subgraph/src/tbtc-utils.ts +++ b/subgraph/src/tbtc-utils.ts @@ -5,6 +5,7 @@ import { ByteArray, BigInt, Bytes, + dataSource, } from "@graphprotocol/graph-ts" const DEPOSIT_REVEALED_EVENT_SIGNATURE = crypto.keccak256( @@ -13,15 +14,14 @@ const DEPOSIT_REVEALED_EVENT_SIGNATURE = crypto.keccak256( ), ) -// TODO: Set correct address for mainnet. -const TBTC_V2_BRIDGE_CONTRACT_ADDRESS = Address.fromString( - "0x9b1a7fE5a16A15F2f9475C5B231750598b113403", -) - // eslint-disable-next-line import/prefer-default-export export function findBitcoinTransactionIdFromTransactionReceipt( transactionReceipt: ethereum.TransactionReceipt | null, ): string { + const tbtcV2BridgeAddress = Address.fromBytes( + dataSource.context().getBytes("tbtcBridgeAddress"), + ) + if (!transactionReceipt) { throw new Error("Transaction receipt not available") } @@ -31,11 +31,17 @@ export function findBitcoinTransactionIdFromTransactionReceipt( // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion const receipt = transactionReceipt as ethereum.TransactionReceipt - const depositRevealedLogIndex = receipt.logs.findIndex( - (receiptLog) => - receiptLog.address.equals(TBTC_V2_BRIDGE_CONTRACT_ADDRESS) && - receiptLog.topics[0].equals(DEPOSIT_REVEALED_EVENT_SIGNATURE), - ) + let depositRevealedLogIndex = -1 + for (let i = 0; i < receipt.logs.length; i += 1) { + const receiptLog = receipt.logs[i] + + if ( + receiptLog.address.equals(tbtcV2BridgeAddress) && + receiptLog.topics[0].equals(DEPOSIT_REVEALED_EVENT_SIGNATURE) + ) { + depositRevealedLogIndex = i + } + } if (depositRevealedLogIndex < 0) { throw new Error("Cannot find `DepositRevealed` event in transaction logs") diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 96ba12d8e..6827437a0 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -7,6 +7,10 @@ dataSources: - kind: ethereum name: BitcoinDepositor network: sepolia + context: + tbtcBridgeAddress: + type: Bytes + data: "0x9b1a7fE5a16A15F2f9475C5B231750598b113403" source: address: "0x2F86FE8C5683372Db667E6f6d88dcB6d55a81286" abi: BitcoinDepositor diff --git a/subgraph/tests/bitcoin-depositor.test.ts b/subgraph/tests/bitcoin-depositor.test.ts index 4fc485dd3..9b4fd5ee4 100644 --- a/subgraph/tests/bitcoin-depositor.test.ts +++ b/subgraph/tests/bitcoin-depositor.test.ts @@ -5,9 +5,15 @@ import { clearStore, beforeAll, afterAll, + dataSourceMock, } from "matchstick-as/assembly/index" -import { BigInt, Address } from "@graphprotocol/graph-ts" +import { + BigInt, + Address, + DataSourceContext, + Bytes, +} from "@graphprotocol/graph-ts" import { createDepositInitializedEvent, createDepositFinalizedEvent, @@ -66,6 +72,19 @@ const depositFinalizedEvent = createDepositFinalizedEvent( depositorFee, ) +// Set up context +const context = new DataSourceContext() +context.setBytes( + "tbtcBridgeAddress", + Bytes.fromHexString("0x9b1a7fE5a16A15F2f9475C5B231750598b113403"), +) + +dataSourceMock.setReturnValues( + "0x2F86FE8C5683372Db667E6f6d88dcB6d55a81286", + "sepolia", + context, +) + describe("handleDepositInitialized", () => { describe("when the deposit owner doesn't exist yet", () => { beforeAll(() => { From 2c94f79332cf4600ac79fab49c721c90e92d250c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 2 May 2024 15:19:32 +0200 Subject: [PATCH 30/51] Pausing token transfers upon pausing the protocol Paused stBTC token transfers once the protocol is paused. Currently mint, deposit, withdraw, and redeem functions can be paused, but not transfer of the tokens. --- solidity/contracts/stBTC.sol | 15 +++++++++++++++ solidity/test/stBTC.test.ts | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/solidity/contracts/stBTC.sol b/solidity/contracts/stBTC.sol index 458364380..2ce9cf8d9 100644 --- a/solidity/contracts/stBTC.sol +++ b/solidity/contracts/stBTC.sol @@ -291,6 +291,21 @@ contract stBTC is ERC4626Fees, PausableOwnable { return convertToAssets(balanceOf(account)); } + /// @dev Transfers a `value` amount of tokens from `from` to `to`, or + /// alternatively mints (or burns) if `from` (or `to`) is the zero + /// address. All customizations to transfers, mints, and burns should + /// be done by overriding this function. + /// @param from Sender of tokens. + /// @param to Receiver of tokens. + /// @param value Amount of tokens to transfer. + function _update( + address from, + address to, + uint256 value + ) internal override whenNotPaused { + super._update(from, to, value); + } + /// @return Returns entry fee basis point used in deposits. function _entryFeeBasisPoints() internal view override returns (uint256) { return entryFeeBasisPoints; diff --git a/solidity/test/stBTC.test.ts b/solidity/test/stBTC.test.ts index 5cd5e5b1f..860d6b3fe 100644 --- a/solidity/test/stBTC.test.ts +++ b/solidity/test/stBTC.test.ts @@ -1819,6 +1819,25 @@ describe("stBTC", () => { stbtc.connect(depositor1).redeem(amount, depositor1, depositor1), ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") }) + + it("should pause transfers", async () => { + await expect( + stbtc.connect(depositor1).transfer(depositor2, amount), + ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") + }) + + it("should pause transfersFrom", async () => { + await expect( + stbtc + .connect(depositor1) + .approve(depositor2.address, amount) + .then(() => + stbtc + .connect(depositor2) + .transferFrom(depositor1, depositor2, amount), + ), + ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") + }) }) }) From 5201a997fb59368e28002180c4ff972922007028 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 2 May 2024 15:52:37 +0200 Subject: [PATCH 31/51] Returning zero when the protocol is paused To stay compliant with the ERC4626 standard we need to return 0 in max* related functions when the protocol is paused. --- solidity/contracts/stBTC.sol | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/solidity/contracts/stBTC.sol b/solidity/contracts/stBTC.sol index 458364380..d1db858a7 100644 --- a/solidity/contracts/stBTC.sol +++ b/solidity/contracts/stBTC.sol @@ -283,6 +283,46 @@ contract stBTC is ERC4626Fees, PausableOwnable { return super.redeem(shares, receiver, owner); } + /// @dev Returns the maximum amount of the underlying asset that can be + /// deposited into the Vault for the receiver, through a deposit call. + /// If the Vault is paused, returns 0. + function maxDeposit(address) public view override returns (uint256) { + if (paused()) { + return 0; + } + return type(uint256).max; + } + + /// @dev Returns the maximum amount of the Vault shares that can be minted + /// for the receiver, through a mint call. + /// If the Vault is paused, returns 0. + function maxMint(address) public view override returns (uint256) { + if (paused()) { + return 0; + } + return type(uint256).max; + } + + /// @dev Returns the maximum amount of the underlying asset that can be + /// withdrawn from the owner balance in the Vault, through a withdraw call. + /// If the Vault is paused, returns 0. + function maxWithdraw(address owner) public view override returns (uint256) { + if (paused()) { + return 0; + } + return super.maxWithdraw(owner); + } + + /// @dev Returns the maximum amount of Vault shares that can be redeemed from + /// the owner balance in the Vault, through a redeem call. + /// If the Vault is paused, returns 0. + function maxRedeem(address owner) public view override returns (uint256) { + if (paused()) { + return 0; + } + return super.maxRedeem(owner); + } + /// @notice Returns value of assets that would be exchanged for the amount of /// shares owned by the `account`. /// @param account Owner of shares. From 3028a04f06ae9b2f07d13fb380af900e4721f7ec Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 2 May 2024 22:41:22 +0200 Subject: [PATCH 32/51] Adding tests to cover max* related functions --- solidity/test/stBTC.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/solidity/test/stBTC.test.ts b/solidity/test/stBTC.test.ts index 5cd5e5b1f..674b792fe 100644 --- a/solidity/test/stBTC.test.ts +++ b/solidity/test/stBTC.test.ts @@ -1819,6 +1819,22 @@ describe("stBTC", () => { stbtc.connect(depositor1).redeem(amount, depositor1, depositor1), ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") }) + + it("should return 0 when calling maxDeposit", async () => { + expect(await stbtc.maxDeposit(depositor1)).to.be.eq(0) + }) + + it("should return 0 when calling maxMint", async () => { + expect(await stbtc.maxMint(depositor1)).to.be.eq(0) + }) + + it("should return 0 when calling maxRedeem", async () => { + expect(await stbtc.maxRedeem(depositor1)).to.be.eq(0) + }) + + it("should return 0 when calling maxWithdraw", async () => { + expect(await stbtc.maxWithdraw(depositor1)).to.be.eq(0) + }) }) }) From 54fa01030113b5907a4eab8ad028feaf46778b5c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 3 May 2024 10:59:17 +0200 Subject: [PATCH 33/51] Update external links in`README` for subgraph --- subgraph/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subgraph/README.md b/subgraph/README.md index 28d373e25..4255558f5 100644 --- a/subgraph/README.md +++ b/subgraph/README.md @@ -40,9 +40,9 @@ create a private one 1. Install Docker on your local machine: - - Mac: https://docs.docker.com/desktop/install/mac-install/ - - Windows: https://docs.docker.com/desktop/install/windows-install/ - - Linux: https://docs.docker.com/desktop/install/linux-install/ + - [Mac](https://docs.docker.com/desktop/install/mac-install/) + - [Windows](https://docs.docker.com/desktop/install/windows-install/) + - [Linux](https://docs.docker.com/desktop/install/linux-install/) 2. Set the API key in the `docker-compose.yaml` file. From ec17c92399ba8fa2611efdd9b0a807ecbf360924 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 11:48:52 +0200 Subject: [PATCH 34/51] Update export from module to default --- dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx index 845819dc2..11ee48027 100644 --- a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx @@ -6,7 +6,9 @@ type GrantedSeasonPassCardProps = CardProps & { heading: string } -export function GrantedSeasonPassCard(props: GrantedSeasonPassCardProps) { +export default function GrantedSeasonPassCard( + props: GrantedSeasonPassCardProps, +) { const { heading, ...restProps } = props return ( From 54aadcbcf73a309468b6c8bcacd90efc9c9239e3 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 11:51:43 +0200 Subject: [PATCH 35/51] Disable `Card` component's padding --- dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx | 4 ++-- dapp/src/pages/OverviewPage/index.tsx | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx index 11ee48027..e70da5861 100644 --- a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx @@ -13,7 +13,7 @@ export default function GrantedSeasonPassCard( return ( - + {heading} - + + + + ) return ( From 419351ded327fc928bef955c477e4faac156a9a4 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 3 May 2024 11:52:40 +0200 Subject: [PATCH 36/51] Add subgraph deployment and publication instructions --- subgraph/README.md | 60 ++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/subgraph/README.md b/subgraph/README.md index 4255558f5..17ad2b5fe 100644 --- a/subgraph/README.md +++ b/subgraph/README.md @@ -78,39 +78,59 @@ Note: use it only if your subgraph is not created in the local Graph node. ### Deploy the subgraph to Subgraph Studio -1. You need to connect wallet to use Subgraph Studio [Metamask, WalletConnect, Coinbase Wallet or Safe]. +1. Go to [Subgraph Studio](https://thegraph.com/studio/). Connect wallet to use + the Subgraph Studio using Metamask, WalletConnect, Coinbase Wallet or Safe. + Use a dedicated account for the Acre team. + +2. Once the account is connected, all subgraphs are available in the [My + Dashboard](https://thegraph.com/studio/) tab. Select the correct subgraph. + +3. Before being able to deploy subgraph to the Subgraph Studio, you need to + login into your account within the CLI. ``` - https://thegraph.com/studio/ + graph auth --studio ``` -2. We're going to create a Subgraph. To do that you need to click Create a Subgraph button in My Dashboard of Subgraph Studio. - -3. In the next step you'll need to add name of Subgraph and choose indexed blockchain from the list. + The `` can be found on "My Subgraphs" page or subgraph details + page. -4. Once your subgraph has been created in Subgraph Studio you can initialize the subgraph code using this command: +4. Deploying a Subgraph to Subgraph Studio ``` - graph init --studio + graph deploy --studio ``` - The value can be found on your subgraph details page in Subgraph Studio - (https://thegraph.com/docs/en/deploying/deploying-a-subgraph-to-studio/#create-your-subgraph-in-subgraph-studio) + The `` can be found on subgraph details page in the Subgraph + Studio. -5. Before being able to deploy your subgraph to Subgraph Studio, you need to login into your account within the CLI. + After running this command, the CLI will ask for a version label, you can + name it however you want, you can use labels such as 0.1 and 0.2 or use + letters as well such as uniswap-v2-0.1. - ``` - graph auth --studio - ``` +If you have any problems, take a look +[here](https://thegraph.com/docs/en/deploying/deploying-a-subgraph-to-studio/). - The can be found on your "My Subgraphs" page or your subgraph details page. +### Publish the subgraph to the Decentralized Network -6. Deploying a Subgraph to Subgraph Studio +Subgraphs can be published to the decentralized network directly from the +Subgraph Studio dashboard. - ``` - graph deploy --studio - ``` +1. Select the correct subgraph from the Subgraph Studio. + +2. Click the "Publish" button + +3. While you’re going through your publishing flow, you’ll be able to push to + either Arbitrum One or Arbitrum Sepolia. + + - Publishing to Arbitrum Sepolia is free. This will allow you to see how the + subgraph will work in the [Graph Explorer](https://thegraph.com/explorer) + and will allow you to test curation elements. This is recommended for + testing purposes only. - After running this command, the CLI will ask for a version label, you can name it however you want, you can use labels such as 0.1 and 0.2 or use letters as well such as uniswap-v2-0.1. +4. Click the "Publish new Subgraph" button. Once a subgraph is published, it + will be available to view in the [Graph + Explorer](https://thegraph.com/explorer). -7. More information about deploying your subgraph to Subgraph Studio: https://thegraph.com/docs/en/deploying/subgraph-studio/ +If you have any problems, take a look +[here](https://thegraph.com/docs/en/publishing/publishing-a-subgraph/). From 493f99f2cb0dc3af045722b782d9b8fba0198ca4 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 11:52:48 +0200 Subject: [PATCH 37/51] Use width/height shorthand property --- dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx index e70da5861..e829f3a72 100644 --- a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx @@ -19,8 +19,7 @@ export default function GrantedSeasonPassCard( as={IconLock} ml={1} my={0.5} - w={5} - h={5} + boxSize={5} verticalAlign="bottom" /> @@ -29,8 +28,7 @@ export default function GrantedSeasonPassCard( as={IconDiscountCheckFilled} mr={2} my={0.5} - w={5} - h={5} + boxSize={5} verticalAlign="bottom" /> Your seat is secured. From ec0874478408ec3a601417d2f5ce38f210c854e6 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 12:02:01 +0200 Subject: [PATCH 38/51] Refactor `GrantedSeasonPass` component styles --- .../OverviewPage/GrantedSeasonPassCard.tsx | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx index e829f3a72..db46a79ed 100644 --- a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx @@ -1,5 +1,13 @@ import React from "react" -import { Card, CardBody, CardHeader, CardProps, Icon } from "@chakra-ui/react" +import { + Card, + CardBody, + CardHeader, + CardProps, + Text, + HStack, + Icon, +} from "@chakra-ui/react" import { IconDiscountCheckFilled, IconLock } from "@tabler/icons-react" type GrantedSeasonPassCardProps = CardProps & { @@ -13,25 +21,26 @@ export default function GrantedSeasonPassCard( return ( - - {heading} - + + {heading} + - - - Your seat is secured. + + + Your seat is secured. ) From 3cef7f33f76e4e09cf631f125b9263b70d95cae7 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 12:04:55 +0200 Subject: [PATCH 39/51] Rename component `BoostArrow` to `BoostArrowIcon`. Additionally reexported component from root directory --- dapp/src/assets/icons/{BoostArrow.tsx => BoostArrowIcon.tsx} | 2 +- dapp/src/assets/icons/index.ts | 1 + dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) rename dapp/src/assets/icons/{BoostArrow.tsx => BoostArrowIcon.tsx} (99%) diff --git a/dapp/src/assets/icons/BoostArrow.tsx b/dapp/src/assets/icons/BoostArrowIcon.tsx similarity index 99% rename from dapp/src/assets/icons/BoostArrow.tsx rename to dapp/src/assets/icons/BoostArrowIcon.tsx index 7f94b0d6e..f0bc1da59 100644 --- a/dapp/src/assets/icons/BoostArrow.tsx +++ b/dapp/src/assets/icons/BoostArrowIcon.tsx @@ -1,7 +1,7 @@ import React from "react" import { createIcon } from "@chakra-ui/react" -export const BoostArrow = createIcon({ +export const BoostArrowIcon = createIcon({ displayName: "BoostArrow", viewBox: "0 0 230 300", defaultProps: { w: 230, h: 300 }, diff --git a/dapp/src/assets/icons/index.ts b/dapp/src/assets/icons/index.ts index d786957f5..62d802cf8 100644 --- a/dapp/src/assets/icons/index.ts +++ b/dapp/src/assets/icons/index.ts @@ -17,3 +17,4 @@ export * from "./LoadingSpinnerSuccessIcon" export * from "./BitcoinIcon" export * from "./EthereumIcon" export * from "./CableWithPlugIcon" +export * from "./BoostArrowIcon" diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index a91025507..5b02df58a 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -16,7 +16,7 @@ import { CurrencyBalanceProps, } from "#/components/shared/CurrencyBalance" import IconTag from "#/components/shared/IconTag" -import { BoostArrow } from "#/assets/icons/BoostArrow" +import { BoostArrowIcon } from "#/assets/icons" type AmountType = CurrencyBalanceProps["amount"] type DashboardCardProps = CardProps & { @@ -70,7 +70,7 @@ export default function DashboardCard(props: DashboardCardProps) { /> - Rewards Boost + Rewards Boost From ea7929f64143892da66d688e442ec1d43421d701 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 12:17:28 +0200 Subject: [PATCH 40/51] Replace `CurrencyBalance` component Removed redundant `DashboardCard`'s prop --- .../DashboardCard/DashboardCard.tsx | 40 +++++++++---------- dapp/src/pages/OverviewPage/index.tsx | 7 ++++ 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index 5b02df58a..73e58455e 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -11,22 +11,19 @@ import { VStack, } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" -import { - CurrencyBalance, - CurrencyBalanceProps, -} from "#/components/shared/CurrencyBalance" +import { CurrencyBalanceProps } from "#/components/shared/CurrencyBalance" import IconTag from "#/components/shared/IconTag" import { BoostArrowIcon } from "#/assets/icons" +import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" type AmountType = CurrencyBalanceProps["amount"] type DashboardCardProps = CardProps & { bitcoinAmount: AmountType - usdAmount: AmountType positionPercentage?: number // TODO: Make this required in post MVP phase } export default function DashboardCard(props: DashboardCardProps) { - const { bitcoinAmount, usdAmount, positionPercentage, ...restProps } = props + const { bitcoinAmount, positionPercentage, ...restProps } = props return ( @@ -52,21 +49,22 @@ export default function DashboardCard(props: DashboardCardProps) { - - diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 2a7433b92..225ce3967 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -8,8 +8,15 @@ import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { DocsCard } from "./DocsCard" import { ActivityCarousel } from "./ActivityCarousel" +import { DashboardCard } from "./DashboardCard" export default function OverviewPage() { + return ( + + + + ) + return ( From 63466e78281a7ca7f0bd8f746c318c6085ac2f95 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 12:21:01 +0200 Subject: [PATCH 41/51] Define `AmountType` type definition Removed unused import --- dapp/src/components/shared/CurrencyBalance/index.tsx | 4 ++-- dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx | 3 +-- dapp/src/types/currency.ts | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dapp/src/components/shared/CurrencyBalance/index.tsx b/dapp/src/components/shared/CurrencyBalance/index.tsx index 7302a7d40..e14fcc0d6 100644 --- a/dapp/src/components/shared/CurrencyBalance/index.tsx +++ b/dapp/src/components/shared/CurrencyBalance/index.tsx @@ -5,11 +5,11 @@ import { getCurrencyByType, numberToLocaleString, } from "#/utils" -import { CurrencyType } from "#/types" +import { CurrencyType, AmountType } from "#/types" export type CurrencyBalanceProps = { currency: CurrencyType - amount?: string | number | bigint + amount?: AmountType shouldBeFormatted?: boolean desiredDecimals?: number size?: string diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index 73e58455e..31e65d45a 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -11,12 +11,11 @@ import { VStack, } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" -import { CurrencyBalanceProps } from "#/components/shared/CurrencyBalance" import IconTag from "#/components/shared/IconTag" import { BoostArrowIcon } from "#/assets/icons" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" +import { AmountType } from "#/types" -type AmountType = CurrencyBalanceProps["amount"] type DashboardCardProps = CardProps & { bitcoinAmount: AmountType positionPercentage?: number // TODO: Make this required in post MVP phase diff --git a/dapp/src/types/currency.ts b/dapp/src/types/currency.ts index e717cdc76..2285a901d 100644 --- a/dapp/src/types/currency.ts +++ b/dapp/src/types/currency.ts @@ -6,3 +6,5 @@ export type Currency = { } export type CurrencyType = "bitcoin" | "ethereum" | "usd" | "stbtc" + +export type AmountType = string | number | bigint From 9062e636f9ac5c9949adf1f92a1f205474071ea3 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 12:34:37 +0200 Subject: [PATCH 42/51] Remove `xl` variant of Button component Defined styles inline instead --- .../OverviewPage/DashboardCard/DashboardCard.tsx | 12 ++++++++++-- dapp/src/theme/Button.ts | 10 +--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx index 31e65d45a..7ab3cbea3 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx @@ -72,17 +72,25 @@ export default function DashboardCard(props: DashboardCardProps) { diff --git a/dapp/src/theme/Button.ts b/dapp/src/theme/Button.ts index 959171596..5b221fb44 100644 --- a/dapp/src/theme/Button.ts +++ b/dapp/src/theme/Button.ts @@ -8,20 +8,12 @@ export const buttonTheme: ComponentSingleStyleConfig = { sizes: { md: { fontSize: "sm", - py: "0.5rem", + py: 2, borderRadius: "md", }, lg: { fontSize: "md", - py: "1rem", - borderRadius: "lg", - }, - xl: { - fontSize: "md", - fontWeight: "bold", - lineHeight: 6, py: 4, - px: 7, borderRadius: "lg", }, }, From cecc25e4b5b6c274b22bfc70acbfad9dd4d370ba Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 12:37:35 +0200 Subject: [PATCH 43/51] Remove redundant component grouping Removed unused prop definition --- .../pages/OverviewPage/{DashboardCard => }/DashboardCard.tsx | 0 dapp/src/pages/OverviewPage/DashboardCard/index.ts | 1 - dapp/src/pages/OverviewPage/index.tsx | 4 ++-- 3 files changed, 2 insertions(+), 3 deletions(-) rename dapp/src/pages/OverviewPage/{DashboardCard => }/DashboardCard.tsx (100%) delete mode 100644 dapp/src/pages/OverviewPage/DashboardCard/index.ts diff --git a/dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard.tsx similarity index 100% rename from dapp/src/pages/OverviewPage/DashboardCard/DashboardCard.tsx rename to dapp/src/pages/OverviewPage/DashboardCard.tsx diff --git a/dapp/src/pages/OverviewPage/DashboardCard/index.ts b/dapp/src/pages/OverviewPage/DashboardCard/index.ts deleted file mode 100644 index 45e4d8a2c..000000000 --- a/dapp/src/pages/OverviewPage/DashboardCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as DashboardCard } from "./DashboardCard" diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 225ce3967..93e2e945f 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -8,12 +8,12 @@ import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { DocsCard } from "./DocsCard" import { ActivityCarousel } from "./ActivityCarousel" -import { DashboardCard } from "./DashboardCard" +import DashboardCard from "./DashboardCard" export default function OverviewPage() { return ( - + ) From ac30bbffac0e183d42675719493d7f7d221b52a6 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 3 May 2024 12:45:11 +0200 Subject: [PATCH 44/51] Add a description about adding signal for subgraph --- subgraph/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/subgraph/README.md b/subgraph/README.md index 17ad2b5fe..ad586ab07 100644 --- a/subgraph/README.md +++ b/subgraph/README.md @@ -128,7 +128,15 @@ Subgraph Studio dashboard. and will allow you to test curation elements. This is recommended for testing purposes only. -4. Click the "Publish new Subgraph" button. Once a subgraph is published, it +4. During the publication flow, it is possible to add signal to your subgraph. + This is not a required step and you can add GRT signal to a published + subgraph from the Graph Explorer later. + + - Adding signal to a subgraph which is not eligible for rewards will not + attract additional Indexers. More info + [here](https://thegraph.com/docs/en/publishing/publishing-a-subgraph/#adding-signal-to-your-subgraph). + +5. Click the "Publish new Subgraph" button. Once a subgraph is published, it will be available to view in the [Graph Explorer](https://thegraph.com/explorer). From e1b991e5f84989999569abe70b5f4ca639a176c5 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Fri, 3 May 2024 12:53:54 +0200 Subject: [PATCH 45/51] Update documentation in HttpApi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Czajkowski <57687279+r-czajkowski@users.noreply.github.com> --- sdk/src/lib/api/HttpApi.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/src/lib/api/HttpApi.ts b/sdk/src/lib/api/HttpApi.ts index 351cd8f6e..d1759d6d2 100644 --- a/sdk/src/lib/api/HttpApi.ts +++ b/sdk/src/lib/api/HttpApi.ts @@ -9,10 +9,10 @@ export default abstract class HttpApi { } /** - * Factory function for running GET requests - * @param endpoint api endpoint - * @param requestInit additional data passed to request - * @returns api response + * Factory function for running GET requests. + * @param endpoint API endpoint. + * @param requestInit Additional data passed to request. + * @returns API response. */ protected async getRequest( endpoint: string, @@ -25,11 +25,11 @@ export default abstract class HttpApi { } /** - * Factory function for running POST requests - * @param endpoint api endpoint - * @param body data passed to POST request - * @param requestInit additional data passed to request - * @returns api response + * Factory function for running POST requests. + * @param endpoint API endpoint, + * @param body Data passed to POST request. + * @param requestInit Additional data passed to request. + * @returns API response. */ protected async postRequest( endpoint: string, From debcf78f3b3a60fe710ca2ca2d2eefa3aba00b7e Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 13:03:58 +0200 Subject: [PATCH 46/51] Define styles as reusable object --- dapp/src/pages/OverviewPage/DashboardCard.tsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/dapp/src/pages/OverviewPage/DashboardCard.tsx b/dapp/src/pages/OverviewPage/DashboardCard.tsx index 7ab3cbea3..53bd4b16d 100644 --- a/dapp/src/pages/OverviewPage/DashboardCard.tsx +++ b/dapp/src/pages/OverviewPage/DashboardCard.tsx @@ -9,6 +9,7 @@ import { Tag, HStack, VStack, + ButtonProps, } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" import IconTag from "#/components/shared/IconTag" @@ -16,6 +17,16 @@ import { BoostArrowIcon } from "#/assets/icons" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { AmountType } from "#/types" +const buttonStyles: ButtonProps = { + size: "lg", + flex: 1, + maxW: "12.5rem", // 200px + fontWeight: "bold", + lineHeight: 6, + px: 7, + h: "auto", +} + type DashboardCardProps = CardProps & { bitcoinAmount: AmountType positionPercentage?: number // TODO: Make this required in post MVP phase @@ -71,27 +82,8 @@ export default function DashboardCard(props: DashboardCardProps) { - - + From 38e51d30b7d5c3612c0d72acb1e06c4c8d1b0963 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 13:04:22 +0200 Subject: [PATCH 47/51] Remove testing JSX --- dapp/src/pages/OverviewPage/index.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 93e2e945f..6e64cca62 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -11,12 +11,6 @@ import { ActivityCarousel } from "./ActivityCarousel" import DashboardCard from "./DashboardCard" export default function OverviewPage() { - return ( - - - - ) - return ( From 2f3270b86f9e5b55abd225046be095f95e5e6d7e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 3 May 2024 13:50:53 +0200 Subject: [PATCH 48/51] Removing whenNotPaused modifier in favor of the paused() check The following functions: deposit, withdraw, redeem, mint already check if the contract is paused or not. This is validated in max* related functions downstream, e.g. maxDeposit(). If the protocol is paused, these max* functions will return zero and stop the flow by reverting a transaction. One of the revert errors might be e.g. ERC4626ExceededMaxDeposit. This way we simplify the flow a little bit and save some gas. --- solidity/contracts/stBTC.sol | 8 ++++---- solidity/test/stBTC.test.ts | 27 +++++++++++++++++---------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/solidity/contracts/stBTC.sol b/solidity/contracts/stBTC.sol index d1db858a7..9d028efeb 100644 --- a/solidity/contracts/stBTC.sol +++ b/solidity/contracts/stBTC.sol @@ -209,7 +209,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { function deposit( uint256 assets, address receiver - ) public override whenNotPaused returns (uint256) { + ) public override returns (uint256) { if (assets < minimumDepositAmount) { revert LessThanMinDeposit(assets, minimumDepositAmount); } @@ -234,7 +234,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { function mint( uint256 shares, address receiver - ) public override whenNotPaused returns (uint256 assets) { + ) public override returns (uint256 assets) { if ((assets = super.mint(shares, receiver)) < minimumDepositAmount) { revert LessThanMinDeposit(assets, minimumDepositAmount); } @@ -251,7 +251,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { uint256 assets, address receiver, address owner - ) public override whenNotPaused returns (uint256) { + ) public override returns (uint256) { uint256 currentAssetsBalance = IERC20(asset()).balanceOf(address(this)); // If there is not enough assets in stBTC to cover user withdrawals and // withdrawal fees then pull the assets from the dispatcher. @@ -273,7 +273,7 @@ contract stBTC is ERC4626Fees, PausableOwnable { uint256 shares, address receiver, address owner - ) public override whenNotPaused returns (uint256) { + ) public override returns (uint256) { uint256 assets = convertToAssets(shares); uint256 currentAssetsBalance = IERC20(asset()).balanceOf(address(this)); if (assets > currentAssetsBalance) { diff --git a/solidity/test/stBTC.test.ts b/solidity/test/stBTC.test.ts index 674b792fe..5481ab052 100644 --- a/solidity/test/stBTC.test.ts +++ b/solidity/test/stBTC.test.ts @@ -1793,31 +1793,38 @@ describe("stBTC", () => { beforeAfterSnapshotWrapper() before(async () => { + await tbtc.mint(depositor1.address, amount) + await tbtc.connect(depositor1).approve(await stbtc.getAddress(), amount) + await stbtc.connect(depositor1).deposit(amount, depositor1) await stbtc.connect(pauseAdmin).pause() }) it("should pause deposits", async () => { - await expect( - stbtc.connect(depositor1).deposit(amount, depositor1), - ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") + await expect(stbtc.connect(depositor1).deposit(amount, depositor1)) + .to.be.revertedWithCustomError(stbtc, "ERC4626ExceededMaxDeposit") + .withArgs(depositor1.address, amount, 0) }) it("should pause minting", async () => { - await expect( - stbtc.connect(depositor1).mint(amount, depositor1), - ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") + await expect(stbtc.connect(depositor1).mint(amount, depositor1)) + .to.be.revertedWithCustomError(stbtc, "ERC4626ExceededMaxMint") + .withArgs(depositor1.address, amount, 0) }) it("should pause withdrawals", async () => { await expect( - stbtc.connect(depositor1).withdraw(amount, depositor1, depositor1), - ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") + stbtc.connect(depositor1).withdraw(to1e18(1), depositor1, depositor1), + ) + .to.be.revertedWithCustomError(stbtc, "ERC4626ExceededMaxWithdraw") + .withArgs(depositor1.address, to1e18(1), 0) }) it("should pause redemptions", async () => { await expect( - stbtc.connect(depositor1).redeem(amount, depositor1, depositor1), - ).to.be.revertedWithCustomError(stbtc, "EnforcedPause") + stbtc.connect(depositor1).redeem(to1e18(1), depositor1, depositor1), + ) + .to.be.revertedWithCustomError(stbtc, "ERC4626ExceededMaxRedeem") + .withArgs(depositor1.address, to1e18(1), 0) }) it("should return 0 when calling maxDeposit", async () => { From 7259e362818e84d608d24951dc97cb4ffcaf2c9e Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 3 May 2024 14:49:56 +0200 Subject: [PATCH 49/51] Remove unused import --- dapp/src/pages/OverviewPage/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 6e64cca62..2a7433b92 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -8,7 +8,6 @@ import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { DocsCard } from "./DocsCard" import { ActivityCarousel } from "./ActivityCarousel" -import DashboardCard from "./DashboardCard" export default function OverviewPage() { return ( From 37bc226fc802eae9be7fd67aeec007c9d39ee9a7 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Mon, 6 May 2024 14:38:08 +0200 Subject: [PATCH 50/51] Remove redundant JSX Replace `Text` with `TextMd` --- .../pages/OverviewPage/GrantedSeasonPassCard.tsx | 15 ++++----------- dapp/src/pages/OverviewPage/index.tsx | 6 ------ 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx index db46a79ed..8ad864e54 100644 --- a/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/OverviewPage/GrantedSeasonPassCard.tsx @@ -4,11 +4,11 @@ import { CardBody, CardHeader, CardProps, - Text, HStack, Icon, } from "@chakra-ui/react" import { IconDiscountCheckFilled, IconLock } from "@tabler/icons-react" +import { TextMd } from "#/components/shared/Typography" type GrantedSeasonPassCardProps = CardProps & { heading: string @@ -21,14 +21,8 @@ export default function GrantedSeasonPassCard( return ( - - {heading} + + {heading} - Your seat is secured. + Your seat is secured. ) diff --git a/dapp/src/pages/OverviewPage/index.tsx b/dapp/src/pages/OverviewPage/index.tsx index 6fa60acc3..2a7433b92 100644 --- a/dapp/src/pages/OverviewPage/index.tsx +++ b/dapp/src/pages/OverviewPage/index.tsx @@ -8,14 +8,8 @@ import Statistics from "./Statistics" import TransactionHistory from "./TransactionHistory" import { DocsCard } from "./DocsCard" import { ActivityCarousel } from "./ActivityCarousel" -import GrantedSeasonPassCard from "./GrantedSeasonPassCard" export default function OverviewPage() { - return ( - - - - ) return ( From c2308c2d739d45e6e88cb9b674b1157602795392 Mon Sep 17 00:00:00 2001 From: Jakub Nowakowski Date: Tue, 7 May 2024 09:52:17 +0200 Subject: [PATCH 51/51] Round up depositor fee calculation Due to the incorrect rounding, unless the bridged amount is a multiple of the depositorFeeDivisor, a loss of 1 wei of fees will occur on every bridging attempt. Additionally, depositorFeeDivisor - 1 tokens can be bridged without any fees. To avoid that we round up the calculation. --- solidity/contracts/BitcoinDepositor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/BitcoinDepositor.sol b/solidity/contracts/BitcoinDepositor.sol index 2251dbd57..bbcaa7784 100644 --- a/solidity/contracts/BitcoinDepositor.sol +++ b/solidity/contracts/BitcoinDepositor.sol @@ -261,7 +261,7 @@ contract BitcoinDepositor is AbstractTBTCDepositor, Ownable2StepUpgradeable { // Compute depositor fee. The fee is calculated based on the initial funding // transaction amount, before the tBTC protocol network fees were taken. uint256 depositorFee = depositorFeeDivisor > 0 - ? (initialAmount / depositorFeeDivisor) + ? Math.ceilDiv(initialAmount, depositorFeeDivisor) : 0; // Ensure the depositor fee does not exceed the approximate minted tBTC