From 2b3a1a3f79928ab6b3073e65e18a2c77a4acb767 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 29 Feb 2024 16:47:10 +0100 Subject: [PATCH 01/23] Add `depositorFeeDivisor` to Depositor contract To match the contract API from #91. --- core/contracts/TbtcDepositor.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/contracts/TbtcDepositor.sol b/core/contracts/TbtcDepositor.sol index 732d289c0..d2644278d 100644 --- a/core/contracts/TbtcDepositor.sol +++ b/core/contracts/TbtcDepositor.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.21; import "@keep-network/tbtc-v2/contracts/integrator/AbstractTBTCDepositor.sol"; contract TbtcDepositor is AbstractTBTCDepositor { + uint64 public depositorFeeDivisor; + function initializeStakeRequest( IBridgeTypes.BitcoinTxInfo calldata fundingTx, IBridgeTypes.DepositRevealInfo calldata reveal, From 61dc761f2d2b7d2864fbc47938d25d2fb86fc7fd Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 29 Feb 2024 16:58:00 +0100 Subject: [PATCH 02/23] Estimate staking fees Add function that estimates the staking fees. Returns the following fees for staking operation: - treasuryFee - the tBTC treasury fee taken from each deposit and transferred to the treasury upon sweep proof submission. Is calculated based on the initial funding transaction amount, - optimisticMintingFee - the tBTC optimistic minting fee, Is calculated AFTER the treasury fee is cut, - depositTxMaxFee - maximum amount of BTC transaction fee that can be incurred by each swept deposit being part of the given sweep transaction, - depositorFee - the Acre network depositor fee taken from each deposit and transferred to the treasury upon stake request finalization. --- sdk/src/lib/contracts/tbtc-depositor.ts | 27 +++ sdk/src/lib/ethereum/tbtc-depositor.ts | 90 ++++++++++ sdk/test/lib/ethereum/tbtc-depositor.test.ts | 169 ++++++++++++++++++- sdk/test/utils/mock-acre-contracts.ts | 1 + 4 files changed, 279 insertions(+), 8 deletions(-) diff --git a/sdk/src/lib/contracts/tbtc-depositor.ts b/sdk/src/lib/contracts/tbtc-depositor.ts index 393221c36..ba14725b2 100644 --- a/sdk/src/lib/contracts/tbtc-depositor.ts +++ b/sdk/src/lib/contracts/tbtc-depositor.ts @@ -9,6 +9,31 @@ export type DecodedExtraData = { referral: number } +export type StakingFees = { + /** + * The tBTC treasury fee taken from each deposit and transferred to the + * treasury upon sweep proof submission. Is calculated based on the initial + * funding transaction amount. + */ + treasuryFee: bigint + /** + * The tBTC optimistic minting fee, Is calculated AFTER the treasury fee is + * cut. + */ + optimisticMintingFee: bigint + /** + * Maximum amount of BTC transaction fee that can + * be incurred by each swept deposit being part of the given sweep + * transaction. + */ + depositTxMaxFee: bigint + /** + * The Acre network depositor fee taken from each deposit and transferred to + * the treasury upon stake request finalization. + */ + depositorFee: bigint +} + /** * Interface for communication with the TBTCDepositor on-chain contract. */ @@ -35,4 +60,6 @@ export interface TBTCDepositor extends DepositorProxy { * @param extraData Encoded extra data. */ decodeExtraData(extraData: string): DecodedExtraData + + estimateStakingFees(amountToStake: bigint): Promise } diff --git a/sdk/src/lib/ethereum/tbtc-depositor.ts b/sdk/src/lib/ethereum/tbtc-depositor.ts index d46ddd310..8fa34cd30 100644 --- a/sdk/src/lib/ethereum/tbtc-depositor.ts +++ b/sdk/src/lib/ethereum/tbtc-depositor.ts @@ -7,12 +7,14 @@ import { isAddress, solidityPacked, zeroPadBytes, + Contract, } from "ethers" import { ChainIdentifier, DecodedExtraData, TBTCDepositor, DepositReceipt, + StakingFees, } from "../contracts" import { BitcoinRawTxVectors } from "../bitcoin" import { EthereumAddress } from "./address" @@ -26,6 +28,11 @@ import { EthereumNetwork } from "./network" import SepoliaTbtcDepositor from "./artifacts/sepolia/TbtcDepositor.json" +type TbtcDepositParameters = { + depositTreasuryFeeDivisor: bigint + depositTxMaxFee: bigint +} + /** * Ethereum implementation of the TBTCDepositor. */ @@ -36,6 +43,15 @@ class EthereumTBTCDepositor extends EthersContractWrapper implements TBTCDepositor { + /** + * Multiplier to convert satoshi to tBTC token units. + */ + readonly #satoshiMultiplier = 10n ** 10n + + #tbtcBridgeDepositsParameters: TbtcDepositParameters | undefined + + #tbtcOptimisticMintingFeeDivisor: bigint | undefined + constructor(config: EthersContractConfig, network: EthereumNetwork) { let artifact: EthersContractDeployment @@ -128,6 +144,80 @@ class EthereumTBTCDepositor return { staker, referral } } + + async estimateStakingFees(amountToStake: bigint): Promise { + const { depositTreasuryFeeDivisor, depositTxMaxFee } = + await this.#getTbtcDepositParameters() + + const treasuryFee = amountToStake / depositTreasuryFeeDivisor + + // Both deposit amount and treasury fee are in the 1e8 satoshi precision. + // We need to convert them to the 1e18 TBTC precision. + const amountSubTreasury = + (amountToStake - treasuryFee) * this.#satoshiMultiplier + + const optimisticMintingFeeDivisor = + await this.#getTbtcOptimisticMintingFeeDivisor() + const optimisticMintingFee = + optimisticMintingFeeDivisor > 0 + ? amountSubTreasury / optimisticMintingFeeDivisor + : 0n + + const depositorFeeDivisor = await this.instance.depositorFeeDivisor() + // Compute depositor fee. The fee is calculated based on the initial funding + // transaction amount, before the tBTC protocol network fees were taken. + const depositorFee = + depositorFeeDivisor > 0n + ? (amountToStake * this.#satoshiMultiplier) / depositorFeeDivisor + : 0n + + // TODO: Maybe we should group fees by network? Eg.: + // `const fess = { tbtc: {...}, acre: {...}}` + return { + treasuryFee: treasuryFee * this.#satoshiMultiplier, + optimisticMintingFee, + depositTxMaxFee: depositTxMaxFee * this.#satoshiMultiplier, + depositorFee, + } + } + + async #getTbtcDepositParameters(): Promise { + if (this.#tbtcBridgeDepositsParameters) { + return this.#tbtcBridgeDepositsParameters + } + + const bridgeAddress = await this.instance.bridge() + + const bridge = new Contract(bridgeAddress, [ + "function depositsParameters()", + ]) + + const depositsParameters = + (await bridge.depositsParameters()) as TbtcDepositParameters + + this.#tbtcBridgeDepositsParameters = depositsParameters + + return depositsParameters + } + + async #getTbtcOptimisticMintingFeeDivisor(): Promise { + if (this.#tbtcOptimisticMintingFeeDivisor) { + return this.#tbtcOptimisticMintingFeeDivisor + } + + const vaultAddress = await this.getTbtcVaultChainIdentifier() + + const vault = new Contract(`0x${vaultAddress.identifierHex}`, [ + "function optimisticMintingFeeDivisor()", + ]) + + const optimisticMintingFeeDivisor = + (await vault.optimisticMintingFeeDivisor()) as bigint + + this.#tbtcOptimisticMintingFeeDivisor = optimisticMintingFeeDivisor + + return optimisticMintingFeeDivisor + } } export { EthereumTBTCDepositor, packRevealDepositParameters } diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 2edca897f..9add365cb 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -1,9 +1,10 @@ -import ethers, { Contract, ZeroAddress } from "ethers" +import ethers, { Contract, ZeroAddress, getAddress } from "ethers" import { EthereumTBTCDepositor, EthereumAddress, Hex, EthereumSigner, + StakingFees, } from "../../../src" import { extraDataValidTestData } from "./data" @@ -12,26 +13,44 @@ jest.mock("ethers", (): object => ({ ...jest.requireActual("ethers"), })) +const testData = { + depositorFeeDivisor: 1000n, + depositParameters: { + depositTreasuryFeeDivisor: 2_000n, // 1/2000 == 5bps == 0.05% == 0.0005 + depositTxMaxFee: 100_000n, // 100000 satoshi = 0.001 BTC + }, + optimisticMintingFeeDivisor: 500n, // 1/500 = 0.002 = 0.2%0 +} + describe("TBTCDepositor", () => { const spyOnEthersDataSlice = jest.spyOn(ethers, "dataSlice") + const spyOnEthersContract = jest.spyOn(ethers, "Contract") const vaultAddress = EthereumAddress.from( ethers.Wallet.createRandom().address, ) + const bridgeAddress = EthereumAddress.from( + ethers.Wallet.createRandom().address, + ) const mockedContractInstance = { - tbtcVault: jest.fn().mockImplementation(() => vaultAddress.identifierHex), + tbtcVault: jest + .fn() + .mockImplementation(() => `0x${vaultAddress.identifierHex}`), initializeStakeRequest: jest.fn(), + bridge: jest.fn().mockResolvedValue(`0x${bridgeAddress.identifierHex}`), + depositorFeeDivisor: jest + .fn() + .mockResolvedValue(testData.depositorFeeDivisor), } + let depositor: EthereumTBTCDepositor let depositorAddress: EthereumAddress - beforeEach(async () => { - jest - .spyOn(ethers, "Contract") - .mockImplementationOnce( - () => mockedContractInstance as unknown as Contract, - ) + beforeAll(async () => { + spyOnEthersContract.mockImplementationOnce( + () => mockedContractInstance as unknown as Contract, + ) // TODO: get the address from artifact imported from `core` package. depositorAddress = EthereumAddress.from( @@ -243,4 +262,138 @@ describe("TBTCDepositor", () => { }, ) }) + + describe("estimateStakingFees", () => { + const mockedBridgeContractInstance = { + depositsParameters: jest + .fn() + .mockResolvedValue(testData.depositParameters), + } + + const mockedVaultContractInstance = { + optimisticMintingFeeDivisor: jest + .fn() + .mockResolvedValue(testData.optimisticMintingFeeDivisor), + } + + const amountToStake = 10_000_000n // 0.1 BTC + + const expectedResult = { + // The fee is calculated based on the initial funding + // transaction amount. `amountToStake / depositTreasuryFeeDivisor` + // 0.00005 tBTC in 1e18 precision. + treasuryFee: 50000000000000n, + // Maximum amount of BTC transaction fee that can + // be incurred by each swept deposit being part of the given sweep + // transaction. + // 0.001 tBTC in 1e18 precision. + depositTxMaxFee: 1000000000000000n, + // Divisor used to compute the depositor fee taken from each deposit + // and transferred to the treasury upon stake request finalization. + // `depositorFee = depositedAmount / depositorFeeDivisor` + // 0.0001 tBTC in 1e18 precision. + depositorFee: 100000000000000n, + // The optimistic fee is a percentage AFTER + // the treasury fee is cut: + // `fee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor` + // 0.0001999 tBTC in 1e18 precision. + optimisticMintingFee: 199900000000000n, + } + + beforeAll(() => { + spyOnEthersContract.mockClear() + + spyOnEthersContract.mockImplementation((target: string) => { + if (getAddress(target) === getAddress(bridgeAddress.identifierHex)) + return mockedBridgeContractInstance as unknown as Contract + if (getAddress(target) === getAddress(vaultAddress.identifierHex)) + return mockedVaultContractInstance as unknown as Contract + + throw new Error("Cannot create mocked contract instance") + }) + }) + + describe("when network fees are not yet cached", () => { + let result: StakingFees + + beforeAll(async () => { + result = await depositor.estimateStakingFees(amountToStake) + }) + + it("should get the bridge contract address", () => { + expect(mockedContractInstance.bridge).toHaveBeenCalled() + }) + + it("should create the ethers Contract instance of the Bridge contract", () => { + expect(Contract).toHaveBeenNthCalledWith( + 1, + `0x${bridgeAddress.identifierHex}`, + ["function depositsParameters()"], + ) + }) + + it("should get the deposit parameters from chain", () => { + expect( + mockedBridgeContractInstance.depositsParameters, + ).toHaveBeenCalled() + }) + + it("should get the vault contract address", () => { + expect(mockedContractInstance.tbtcVault).toHaveBeenCalled() + }) + + it("should create the ethers Contract instance of the Bridge contract", () => { + expect(Contract).toHaveBeenNthCalledWith( + 2, + `0x${vaultAddress.identifierHex}`, + ["function optimisticMintingFeeDivisor()"], + ) + }) + + it("should get the optimistic minting fee divisor", () => { + expect( + mockedVaultContractInstance.optimisticMintingFeeDivisor, + ).toHaveBeenCalled() + }) + + it("should get the depositor fee divisor", () => { + expect(mockedContractInstance.depositorFeeDivisor).toHaveBeenCalled() + }) + + it("should return correct fees", () => { + expect(result).toMatchObject(expectedResult) + }) + }) + + describe("when network fees are already cached", () => { + let result2: StakingFees + + beforeAll(async () => { + mockedContractInstance.bridge.mockClear() + mockedContractInstance.tbtcVault.mockClear() + mockedBridgeContractInstance.depositsParameters.mockClear() + mockedVaultContractInstance.optimisticMintingFeeDivisor.mockClear() + + result2 = await depositor.estimateStakingFees(amountToStake) + }) + + it("should get the deposit parameters from cache", () => { + expect(mockedContractInstance.bridge).toHaveBeenCalledTimes(0) + expect( + mockedBridgeContractInstance.depositsParameters, + ).toHaveBeenCalledTimes(0) + }) + + it("should get the optimistic minting fee divisor from cache", () => { + expect(mockedContractInstance.tbtcVault).toHaveBeenCalledTimes(0) + expect( + mockedVaultContractInstance.optimisticMintingFeeDivisor, + ).toHaveBeenCalledTimes(0) + }) + + it("should return correct fees", () => { + expect(result2).toMatchObject(expectedResult) + }) + }) + }) }) diff --git a/sdk/test/utils/mock-acre-contracts.ts b/sdk/test/utils/mock-acre-contracts.ts index 111554b39..96c3feb23 100644 --- a/sdk/test/utils/mock-acre-contracts.ts +++ b/sdk/test/utils/mock-acre-contracts.ts @@ -11,6 +11,7 @@ export class MockAcreContracts implements AcreContracts { decodeExtraData: jest.fn(), encodeExtraData: jest.fn(), revealDeposit: jest.fn(), + estimateStakingFees: jest.fn(), } as TBTCDepositor } } From 7bbdcf8e145cb55e376852ab81dc5e811905ab8c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 29 Feb 2024 17:03:56 +0100 Subject: [PATCH 03/23] Expose fee breakdown for staking operation Expose fee breakdown for staking opeartion in staking `module`. --- sdk/src/modules/staking/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index f4fd6b181..94a8853bf 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -1,5 +1,5 @@ import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" -import { AcreContracts, DepositorProxy } from "../../lib/contracts" +import { AcreContracts, DepositorProxy, StakingFees } from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" @@ -62,6 +62,10 @@ class StakingModule { deposit, ) } + + async estimateStakingFees(amount: bigint): Promise { + return this.#contracts.tbtcDepositor.estimateStakingFees(amount) + } } export { StakingModule, StakeInitialization } From 166dac971bd24625dc9bc7653914e84f5bffa104 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 14 Mar 2024 14:05:24 +0100 Subject: [PATCH 04/23] Group the staking fees by networks We want to group the staking fees by tBTC and Acre networks. --- sdk/src/lib/contracts/bitcoin-depositor.ts | 22 +++++++++- sdk/src/lib/ethereum/bitcoin-depositor.ts | 17 +++++--- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 42 +++++++++++--------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index 58fdc8e61..3e1120dd5 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -9,7 +9,10 @@ export type DecodedExtraData = { referral: number } -export type StakingFees = { +/** + * Represents the tBTC network minting fees. + */ +type TBTCMintingFees = { /** * The tBTC treasury fee taken from each deposit and transferred to the * treasury upon sweep proof submission. Is calculated based on the initial @@ -27,6 +30,12 @@ export type StakingFees = { * transaction. */ depositTxMaxFee: bigint +} + +/** + * Represents the Acre network staking fees. + */ +type AcreStakingFees = { /** * The Acre network depositor fee taken from each deposit and transferred to * the treasury upon stake request finalization. @@ -34,6 +43,11 @@ export type StakingFees = { depositorFee: bigint } +export type StakingFees = { + tbtc: TBTCMintingFees + acre: AcreStakingFees +} + /** * Interface for communication with the AcreBitcoinDepositor on-chain contract. */ @@ -61,5 +75,11 @@ export interface BitcoinDepositor extends DepositorProxy { */ decodeExtraData(extraData: string): DecodedExtraData + /** + * Estimates the staking fees based on the provided amount. + * @param amountToStake Amount to stake in 1e8 satoshi precision. + * @returns Staking fees grouped by tBTC and Acre networks in 1e18 tBTC token + * precision. + */ estimateStakingFees(amountToStake: bigint): Promise } diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 7d6c7d594..f512461b7 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -145,6 +145,9 @@ class EthereumBitcoinDepositor return { staker, referral } } + /** + * @see {BitcoinDepositor#estimateStakingFees} + */ async estimateStakingFees(amountToStake: bigint): Promise { const { depositTreasuryFeeDivisor, depositTxMaxFee } = await this.#getTbtcDepositParameters() @@ -171,13 +174,15 @@ class EthereumBitcoinDepositor ? (amountToStake * this.#satoshiMultiplier) / depositorFeeDivisor : 0n - // TODO: Maybe we should group fees by network? Eg.: - // `const fess = { tbtc: {...}, acre: {...}}` return { - treasuryFee: treasuryFee * this.#satoshiMultiplier, - optimisticMintingFee, - depositTxMaxFee: depositTxMaxFee * this.#satoshiMultiplier, - depositorFee, + tbtc: { + treasuryFee: treasuryFee * this.#satoshiMultiplier, + optimisticMintingFee, + depositTxMaxFee: depositTxMaxFee * this.#satoshiMultiplier, + }, + acre: { + depositorFee, + }, } } diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 93545b3f9..7ef9a6fe5 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -276,25 +276,29 @@ describe("BitcoinDepositor", () => { const amountToStake = 10_000_000n // 0.1 BTC const expectedResult = { - // The fee is calculated based on the initial funding - // transaction amount. `amountToStake / depositTreasuryFeeDivisor` - // 0.00005 tBTC in 1e18 precision. - treasuryFee: 50000000000000n, - // Maximum amount of BTC transaction fee that can - // be incurred by each swept deposit being part of the given sweep - // transaction. - // 0.001 tBTC in 1e18 precision. - depositTxMaxFee: 1000000000000000n, - // Divisor used to compute the depositor fee taken from each deposit - // and transferred to the treasury upon stake request finalization. - // `depositorFee = depositedAmount / depositorFeeDivisor` - // 0.0001 tBTC in 1e18 precision. - depositorFee: 100000000000000n, - // The optimistic fee is a percentage AFTER - // the treasury fee is cut: - // `fee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor` - // 0.0001999 tBTC in 1e18 precision. - optimisticMintingFee: 199900000000000n, + tbtc: { + // The fee is calculated based on the initial funding + // transaction amount. `amountToStake / depositTreasuryFeeDivisor` + // 0.00005 tBTC in 1e18 precision. + treasuryFee: 50000000000000n, + // Maximum amount of BTC transaction fee that can + // be incurred by each swept deposit being part of the given sweep + // transaction. + // 0.001 tBTC in 1e18 precision. + depositTxMaxFee: 1000000000000000n, + // The optimistic fee is a percentage AFTER + // the treasury fee is cut: + // `fee = (depositAmount - treasuryFee) / optimisticMintingFeeDivisor` + // 0.0001999 tBTC in 1e18 precision. + optimisticMintingFee: 199900000000000n, + }, + acre: { + // Divisor used to compute the depositor fee taken from each deposit + // and transferred to the treasury upon stake request finalization. + // `depositorFee = depositedAmount / depositorFeeDivisor` + // 0.0001 tBTC in 1e18 precision. + depositorFee: 100000000000000n, + }, } beforeAll(() => { From b56701455586932396c9ae7035b0c3193c023fda Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 14 Mar 2024 15:52:23 +0100 Subject: [PATCH 05/23] Expose the `estimateStakingFees` in staking module This function returns the staking fees estimated based on the provided amount grouped by tBTC and Acre network fees. Fees are in 1e8 satoshi precision. --- sdk/src/lib/utils/index.ts | 1 + sdk/src/lib/utils/satoshi-converter.ts | 11 ++++ sdk/src/modules/staking/index.ts | 26 +++++++- sdk/test/modules/staking.test.ts | 90 ++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 sdk/src/lib/utils/satoshi-converter.ts diff --git a/sdk/src/lib/utils/index.ts b/sdk/src/lib/utils/index.ts index 294cd1874..70c6b147c 100644 --- a/sdk/src/lib/utils/index.ts +++ b/sdk/src/lib/utils/index.ts @@ -1,3 +1,4 @@ export * from "./hex" export * from "./ethereum-signer" export * from "./backoff" +export * from "./satoshi-converter" diff --git a/sdk/src/lib/utils/satoshi-converter.ts b/sdk/src/lib/utils/satoshi-converter.ts new file mode 100644 index 000000000..8c43de3e2 --- /dev/null +++ b/sdk/src/lib/utils/satoshi-converter.ts @@ -0,0 +1,11 @@ +const BTC_DECIMALS = 8n + +// eslint-disable-next-line import/prefer-default-export +export function toSatoshi(amount: bigint, fromPrecision: bigint = 18n) { + const SATOSHI_MULTIPLIER = 10n ** (fromPrecision - BTC_DECIMALS) + + const remainder = amount % SATOSHI_MULTIPLIER + const satoshis = (amount - remainder) / SATOSHI_MULTIPLIER + + return satoshis +} diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index efea34584..23f9ae3ee 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -2,6 +2,7 @@ import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" import { AcreContracts, DepositorProxy, StakingFees } from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" +import { toSatoshi } from "../../lib/utils" /** * Module exposing features related to the staking. @@ -79,8 +80,29 @@ class StakingModule { return this.#contracts.stBTC.assetsBalanceOf(identifier) } - estimateStakingFees(amount: bigint): Promise { - return this.#contracts.bitcoinDepositor.estimateStakingFees(amount) + /** + * Estimates the staking fees based on the provided amount. + * @param amountToStake Amount to stake in satoshi. + * @returns Staking fees grouped by tBTC and Acre networks in 1e8 satoshi + * precision. + */ + async estimateStakingFees(amount: bigint): Promise { + const { acre, tbtc } = + await this.#contracts.bitcoinDepositor.estimateStakingFees(amount) + + const feesToSatoshi = ( + fees: T, + ) => + Object.entries(fees).reduce( + (reducer, [key, value]) => + Object.assign(reducer, { [key]: toSatoshi(value) }), + {} as T, + ) + + return { + tbtc: feesToSatoshi(tbtc), + acre: feesToSatoshi(acre), + } } } diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index 664e54767..ee76c9146 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -8,7 +8,9 @@ import { DepositorProxy, DepositReceipt, EthereumAddress, + StakingFees, } from "../../src" +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" @@ -21,6 +23,11 @@ const stakingModuleData: { bitcoinRecoveryAddress: string mockedDepositBTCAddress: string } + estimateStakingFees: { + amount: bigint + mockedStakingFees: StakingFees + expectedStakingFeesInSatoshi: StakingFees + } } = { initializeStake: { staker: EthereumAddress.from(ethers.Wallet.createRandom().address), @@ -32,6 +39,37 @@ const stakingModuleData: { mockedDepositBTCAddress: "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05", }, + estimateStakingFees: { + amount: 10_000_000n, // 0.1 BTC + mockedStakingFees: { + tbtc: { + // 0.00005 tBTC in 1e18 precision. + treasuryFee: 50000000000000n, + // 0.001 tBTC in 1e18 precision. + depositTxMaxFee: 1000000000000000n, + // 0.0001999 tBTC in 1e18 precision. + optimisticMintingFee: 199900000000000n, + }, + acre: { + // 0.0001 tBTC in 1e18 precision. + depositorFee: 100000000000000n, + }, + }, + expectedStakingFeesInSatoshi: { + tbtc: { + // 0.00005 BTC in 1e8 satoshi precision. + treasuryFee: 5000n, + // 0.001 BTC in 1e8 satoshi precision. + depositTxMaxFee: 100000n, + // 0.0001999 BTC in 1e8 satoshi precision. + optimisticMintingFee: 19990n, + }, + acre: { + // 0.0001 BTC in 1e8 satoshi precision. + depositorFee: 10000n, + }, + }, + }, } const stakingInitializationData: { @@ -395,4 +433,56 @@ describe("Staking", () => { expect(result).toEqual(expectedResult) }) }) + + describe("estimateStakingFees", () => { + const { + estimateStakingFees: { + amount, + mockedStakingFees, + expectedStakingFeesInSatoshi, + }, + } = stakingModuleData + + let result: StakingFees + const spyOnToSatoshi = jest.spyOn(satoshiConverter, "toSatoshi") + + beforeAll(async () => { + contracts.bitcoinDepositor.estimateStakingFees = jest + .fn() + .mockResolvedValue(mockedStakingFees) + result = await staking.estimateStakingFees(amount) + }) + + it("should get the staking fees from Acre Bitcoin Depositor contract handle", () => { + expect( + contracts.bitcoinDepositor.estimateStakingFees, + ).toHaveBeenCalledWith(amount) + }) + + it("should convert tBTC network fees to satoshi", () => { + expect(spyOnToSatoshi).toHaveBeenNthCalledWith( + 1, + mockedStakingFees.tbtc.treasuryFee, + ) + expect(spyOnToSatoshi).toHaveBeenNthCalledWith( + 2, + mockedStakingFees.tbtc.depositTxMaxFee, + ) + expect(spyOnToSatoshi).toHaveBeenNthCalledWith( + 3, + mockedStakingFees.tbtc.optimisticMintingFee, + ) + }) + + it("should convert Acre network fees to satoshi", () => { + expect(spyOnToSatoshi).toHaveBeenNthCalledWith( + 4, + mockedStakingFees.acre.depositorFee, + ) + }) + + it("should return the staking fees in satoshi precision", () => { + expect(result).toMatchObject(expectedStakingFeesInSatoshi) + }) + }) }) From ac1c50b1dc5bd6124a86cc835e82cfb328e0d6a4 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 14 Mar 2024 15:56:42 +0100 Subject: [PATCH 06/23] Update `estimateStakingFees` Update the `estimateStakingFees` function in Ethereum implementation of the `BitcoinDepositor` contract handle - we want to check if the `depositTreasuryFeeDivisor` value is greater than zero before we do division operation. --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index f512461b7..56a8da7fd 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -152,7 +152,10 @@ class EthereumBitcoinDepositor const { depositTreasuryFeeDivisor, depositTxMaxFee } = await this.#getTbtcDepositParameters() - const treasuryFee = amountToStake / depositTreasuryFeeDivisor + const treasuryFee = + depositTreasuryFeeDivisor > 0 + ? amountToStake / depositTreasuryFeeDivisor + : 0n // Both deposit amount and treasury fee are in the 1e8 satoshi precision. // We need to convert them to the 1e18 TBTC precision. From 657417961dc9495029f72a4b9006b23d41e7df05 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Thu, 14 Mar 2024 16:03:32 +0100 Subject: [PATCH 07/23] Update `estimateStakingFees` Ethereum function Move all the contract parameters getters to the beginning of the `estimateStakingFees` function to improve readability. --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 56a8da7fd..ea327a023 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -149,8 +149,11 @@ class EthereumBitcoinDepositor * @see {BitcoinDepositor#estimateStakingFees} */ async estimateStakingFees(amountToStake: bigint): Promise { - const { depositTreasuryFeeDivisor, depositTxMaxFee } = - await this.#getTbtcDepositParameters() + const { + depositTreasuryFeeDivisor, + depositTxMaxFee, + optimisticMintingFeeDivisor, + } = await this.#getTbtcMintingFeesParameters() const treasuryFee = depositTreasuryFeeDivisor > 0 @@ -162,8 +165,6 @@ class EthereumBitcoinDepositor const amountSubTreasury = (amountToStake - treasuryFee) * this.#satoshiMultiplier - const optimisticMintingFeeDivisor = - await this.#getTbtcOptimisticMintingFeeDivisor() const optimisticMintingFee = optimisticMintingFeeDivisor > 0 ? amountSubTreasury / optimisticMintingFeeDivisor @@ -189,6 +190,19 @@ class EthereumBitcoinDepositor } } + async #getTbtcMintingFeesParameters(): Promise< + TbtcDepositParameters & { optimisticMintingFeeDivisor: bigint } + > { + const depositParameters = await this.#getTbtcDepositParameters() + const optimisticMintingFeeDivisor = + await this.#getTbtcOptimisticMintingFeeDivisor() + + return { + ...depositParameters, + optimisticMintingFeeDivisor, + } + } + async #getTbtcDepositParameters(): Promise { if (this.#tbtcBridgeDepositsParameters) { return this.#tbtcBridgeDepositsParameters From de5628212fce2a147c5c0d7cccbac39cc24549bc Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 18 Mar 2024 10:25:02 +0100 Subject: [PATCH 08/23] Update `estimateStakingFees` in staking module Sum up all network fees and add total field - we decided to add a total field because the SDK should be responsible for summing up all fees. If we add a new fee in the future the consumers will have to update their code as well which is not a developer-friendly approach. --- sdk/src/modules/staking/index.ts | 41 +++++++++++++++++--------- sdk/test/modules/staking.test.ts | 49 +++++++++++++------------------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 23f9ae3ee..44c39dda2 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -1,9 +1,22 @@ import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" -import { AcreContracts, DepositorProxy, StakingFees } from "../../lib/contracts" +import { + AcreContracts, + DepositorProxy, + StakingFees as StakingFeesByNetwork, +} from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" import { toSatoshi } from "../../lib/utils" +/** + * Represents all total staking fees grouped by network. + */ +export type TotalStakingFees = { + tbtc: bigint + acre: bigint + total: bigint +} + /** * Module exposing features related to the staking. */ @@ -84,24 +97,26 @@ class StakingModule { * Estimates the staking fees based on the provided amount. * @param amountToStake Amount to stake in satoshi. * @returns Staking fees grouped by tBTC and Acre networks in 1e8 satoshi - * precision. + * precision and total staking fees value. */ - async estimateStakingFees(amount: bigint): Promise { - const { acre, tbtc } = + async estimateStakingFees(amount: bigint): Promise { + const { acre: acreFees, tbtc: tbtcFees } = await this.#contracts.bitcoinDepositor.estimateStakingFees(amount) - const feesToSatoshi = ( + const sumFeesByNetwork = < + T extends StakingFeesByNetwork["tbtc"] | StakingFeesByNetwork["acre"], + >( fees: T, - ) => - Object.entries(fees).reduce( - (reducer, [key, value]) => - Object.assign(reducer, { [key]: toSatoshi(value) }), - {} as T, - ) + ) => Object.values(fees).reduce((reducer, fee) => reducer + fee, 0n) + + const tbtc = toSatoshi(sumFeesByNetwork(tbtcFees)) + + const acre = toSatoshi(sumFeesByNetwork(acreFees)) return { - tbtc: feesToSatoshi(tbtc), - acre: feesToSatoshi(acre), + tbtc, + acre, + total: tbtc + acre, } } } diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index ee76c9146..b95350ca8 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -9,6 +9,7 @@ import { DepositReceipt, EthereumAddress, StakingFees, + TotalStakingFees, } from "../../src" import * as satoshiConverter from "../../src/lib/utils/satoshi-converter" import { MockAcreContracts } from "../utils/mock-acre-contracts" @@ -26,7 +27,7 @@ const stakingModuleData: { estimateStakingFees: { amount: bigint mockedStakingFees: StakingFees - expectedStakingFeesInSatoshi: StakingFees + expectedStakingFeesInSatoshi: TotalStakingFees } } = { initializeStake: { @@ -56,18 +57,9 @@ const stakingModuleData: { }, }, expectedStakingFeesInSatoshi: { - tbtc: { - // 0.00005 BTC in 1e8 satoshi precision. - treasuryFee: 5000n, - // 0.001 BTC in 1e8 satoshi precision. - depositTxMaxFee: 100000n, - // 0.0001999 BTC in 1e8 satoshi precision. - optimisticMintingFee: 19990n, - }, - acre: { - // 0.0001 BTC in 1e8 satoshi precision. - depositorFee: 10000n, - }, + tbtc: 124990n, + acre: 10000n, + total: 134990n, }, }, } @@ -443,13 +435,14 @@ describe("Staking", () => { }, } = stakingModuleData - let result: StakingFees + let result: TotalStakingFees const spyOnToSatoshi = jest.spyOn(satoshiConverter, "toSatoshi") beforeAll(async () => { contracts.bitcoinDepositor.estimateStakingFees = jest .fn() .mockResolvedValue(mockedStakingFees) + result = await staking.estimateStakingFees(amount) }) @@ -460,25 +453,21 @@ describe("Staking", () => { }) it("should convert tBTC network fees to satoshi", () => { - expect(spyOnToSatoshi).toHaveBeenNthCalledWith( - 1, - mockedStakingFees.tbtc.treasuryFee, - ) - expect(spyOnToSatoshi).toHaveBeenNthCalledWith( - 2, - mockedStakingFees.tbtc.depositTxMaxFee, - ) - expect(spyOnToSatoshi).toHaveBeenNthCalledWith( - 3, - mockedStakingFees.tbtc.optimisticMintingFee, - ) + const { + tbtc: { depositTxMaxFee, treasuryFee, optimisticMintingFee }, + } = mockedStakingFees + const totalTbtcFees = depositTxMaxFee + treasuryFee + optimisticMintingFee + + expect(spyOnToSatoshi).toHaveBeenNthCalledWith(1, totalTbtcFees) }) it("should convert Acre network fees to satoshi", () => { - expect(spyOnToSatoshi).toHaveBeenNthCalledWith( - 4, - mockedStakingFees.acre.depositorFee, - ) + const { + acre: { depositorFee }, + } = mockedStakingFees + const totalAcreFees = depositorFee + + expect(spyOnToSatoshi).toHaveBeenNthCalledWith(2, totalAcreFees) }) it("should return the staking fees in satoshi precision", () => { From 59c15b813ef007de2c901a72db2ef46b6af43322 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 18 Mar 2024 19:11:54 +0100 Subject: [PATCH 09/23] Rename field in `AcreStakingFees` type `depositorFee` -> `bitcoinDepositorFee` --- sdk/src/lib/contracts/bitcoin-depositor.ts | 6 +++--- sdk/src/lib/ethereum/bitcoin-depositor.ts | 2 +- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 2 +- sdk/test/modules/staking.test.ts | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index 3e1120dd5..4966eacf5 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -37,10 +37,10 @@ type TBTCMintingFees = { */ type AcreStakingFees = { /** - * The Acre network depositor fee taken from each deposit and transferred to - * the treasury upon stake request finalization. + * The Acre network depositor fee taken from each Bitcoin deposit and + * transferred to the treasury upon stake request finalization. */ - depositorFee: bigint + bitcoinDepositorFee: bigint } export type StakingFees = { diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index ea327a023..385974700 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -185,7 +185,7 @@ class EthereumBitcoinDepositor depositTxMaxFee: depositTxMaxFee * this.#satoshiMultiplier, }, acre: { - depositorFee, + bitcoinDepositorFee: depositorFee, }, } } diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 7ef9a6fe5..7157c0961 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -297,7 +297,7 @@ describe("BitcoinDepositor", () => { // and transferred to the treasury upon stake request finalization. // `depositorFee = depositedAmount / depositorFeeDivisor` // 0.0001 tBTC in 1e18 precision. - depositorFee: 100000000000000n, + bitcoinDepositorFee: 100000000000000n, }, } diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index b95350ca8..1824a6a64 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -53,7 +53,7 @@ const stakingModuleData: { }, acre: { // 0.0001 tBTC in 1e18 precision. - depositorFee: 100000000000000n, + bitcoinDepositorFee: 100000000000000n, }, }, expectedStakingFeesInSatoshi: { @@ -463,9 +463,9 @@ describe("Staking", () => { it("should convert Acre network fees to satoshi", () => { const { - acre: { depositorFee }, + acre: { bitcoinDepositorFee }, } = mockedStakingFees - const totalAcreFees = depositorFee + const totalAcreFees = bitcoinDepositorFee expect(spyOnToSatoshi).toHaveBeenNthCalledWith(2, totalAcreFees) }) From c706f8890e79b7b73ef691ed00a0d6c385ccee61 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 18 Mar 2024 19:25:20 +0100 Subject: [PATCH 10/23] Combine all tBTC Bridge params in one obj and fn --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 51 +++++++---------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 385974700..d99731cda 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -33,6 +33,10 @@ type TbtcDepositParameters = { depositTxMaxFee: bigint } +type TbtcBridgeMintingParameters = TbtcDepositParameters & { + optimisticMintingFeeDivisor: bigint +} + /** * Ethereum implementation of the BitcoinDepositor. */ @@ -48,9 +52,7 @@ class EthereumBitcoinDepositor */ readonly #satoshiMultiplier = 10n ** 10n - #tbtcBridgeDepositsParameters: TbtcDepositParameters | undefined - - #tbtcOptimisticMintingFeeDivisor: bigint | undefined + #tbtcBridgeMintingParameters: TbtcBridgeMintingParameters | undefined constructor(config: EthersContractConfig, network: EthereumNetwork) { let artifact: EthersContractDeployment @@ -153,7 +155,7 @@ class EthereumBitcoinDepositor depositTreasuryFeeDivisor, depositTxMaxFee, optimisticMintingFeeDivisor, - } = await this.#getTbtcMintingFeesParameters() + } = await this.#getTbtcBridgeMintingParameters() const treasuryFee = depositTreasuryFeeDivisor > 0 @@ -190,55 +192,30 @@ class EthereumBitcoinDepositor } } - async #getTbtcMintingFeesParameters(): Promise< - TbtcDepositParameters & { optimisticMintingFeeDivisor: bigint } - > { - const depositParameters = await this.#getTbtcDepositParameters() - const optimisticMintingFeeDivisor = - await this.#getTbtcOptimisticMintingFeeDivisor() - - return { - ...depositParameters, - optimisticMintingFeeDivisor, - } - } - - async #getTbtcDepositParameters(): Promise { - if (this.#tbtcBridgeDepositsParameters) { - return this.#tbtcBridgeDepositsParameters + async #getTbtcBridgeMintingParameters(): Promise { + if (this.#tbtcBridgeMintingParameters) { + return this.#tbtcBridgeMintingParameters } const bridgeAddress = await this.instance.bridge() - const bridge = new Contract(bridgeAddress, [ "function depositsParameters()", ]) - const depositsParameters = (await bridge.depositsParameters()) as TbtcDepositParameters - this.#tbtcBridgeDepositsParameters = depositsParameters - - return depositsParameters - } - - async #getTbtcOptimisticMintingFeeDivisor(): Promise { - if (this.#tbtcOptimisticMintingFeeDivisor) { - return this.#tbtcOptimisticMintingFeeDivisor - } - const vaultAddress = await this.getTbtcVaultChainIdentifier() - const vault = new Contract(`0x${vaultAddress.identifierHex}`, [ "function optimisticMintingFeeDivisor()", ]) - const optimisticMintingFeeDivisor = (await vault.optimisticMintingFeeDivisor()) as bigint - this.#tbtcOptimisticMintingFeeDivisor = optimisticMintingFeeDivisor - - return optimisticMintingFeeDivisor + this.#tbtcBridgeMintingParameters = { + ...depositsParameters, + optimisticMintingFeeDivisor, + } + return this.#tbtcBridgeMintingParameters } } From a743b88e85cf8b5a2ece21495de406043b8928a2 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 18 Mar 2024 19:41:24 +0100 Subject: [PATCH 11/23] Add `fromSatoshi` utils function Add `fromSatoshi` fn that converts an amount from `1e8` to `1e18` token precision. Also here we remove optional `fromPrecision` param in `toSatoshi` fn - we assume we always convert from `1e18` to `1e8` and in `fromSatoshi` we always convert from `1e8` to `1e18`. --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 16 +++++----------- sdk/src/lib/utils/satoshi-converter.ts | 14 +++++++++----- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index d99731cda..adb6221db 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -23,7 +23,7 @@ import { EthersContractDeployment, EthersContractWrapper, } from "./contract" -import { Hex } from "../utils" +import { Hex, fromSatoshi } from "../utils" import { EthereumNetwork } from "./network" import SepoliaBitcoinDepositor from "./artifacts/sepolia/AcreBitcoinDepositor.json" @@ -47,11 +47,6 @@ class EthereumBitcoinDepositor extends EthersContractWrapper implements BitcoinDepositor { - /** - * Multiplier to convert satoshi to tBTC token units. - */ - readonly #satoshiMultiplier = 10n ** 10n - #tbtcBridgeMintingParameters: TbtcBridgeMintingParameters | undefined constructor(config: EthersContractConfig, network: EthereumNetwork) { @@ -164,8 +159,7 @@ class EthereumBitcoinDepositor // Both deposit amount and treasury fee are in the 1e8 satoshi precision. // We need to convert them to the 1e18 TBTC precision. - const amountSubTreasury = - (amountToStake - treasuryFee) * this.#satoshiMultiplier + const amountSubTreasury = fromSatoshi(amountToStake - treasuryFee) const optimisticMintingFee = optimisticMintingFeeDivisor > 0 @@ -177,14 +171,14 @@ class EthereumBitcoinDepositor // transaction amount, before the tBTC protocol network fees were taken. const depositorFee = depositorFeeDivisor > 0n - ? (amountToStake * this.#satoshiMultiplier) / depositorFeeDivisor + ? fromSatoshi(amountToStake) / depositorFeeDivisor : 0n return { tbtc: { - treasuryFee: treasuryFee * this.#satoshiMultiplier, + treasuryFee: fromSatoshi(treasuryFee), optimisticMintingFee, - depositTxMaxFee: depositTxMaxFee * this.#satoshiMultiplier, + depositTxMaxFee: fromSatoshi(depositTxMaxFee), }, acre: { bitcoinDepositorFee: depositorFee, diff --git a/sdk/src/lib/utils/satoshi-converter.ts b/sdk/src/lib/utils/satoshi-converter.ts index 8c43de3e2..8178a15d4 100644 --- a/sdk/src/lib/utils/satoshi-converter.ts +++ b/sdk/src/lib/utils/satoshi-converter.ts @@ -1,11 +1,15 @@ -const BTC_DECIMALS = 8n - -// eslint-disable-next-line import/prefer-default-export -export function toSatoshi(amount: bigint, fromPrecision: bigint = 18n) { - const SATOSHI_MULTIPLIER = 10n ** (fromPrecision - BTC_DECIMALS) +/** + * Multiplier to convert satoshi to 1e18 precision. + */ +const SATOSHI_MULTIPLIER = 10n ** 10n +export function toSatoshi(amount: bigint) { const remainder = amount % SATOSHI_MULTIPLIER const satoshis = (amount - remainder) / SATOSHI_MULTIPLIER return satoshis } + +export function fromSatoshi(amount: bigint) { + return amount * SATOSHI_MULTIPLIER +} From fb089a590589c18b14cdf520f8c3ac80eb352d46 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 18 Mar 2024 19:46:00 +0100 Subject: [PATCH 12/23] Leave `TODO` We should consider exposing the tBTC Bridge minting parameters from tTBC SDK. --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index adb6221db..781d1fe91 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -186,6 +186,7 @@ class EthereumBitcoinDepositor } } + // TODO: Consider exposing it from tBTC SDK. async #getTbtcBridgeMintingParameters(): Promise { if (this.#tbtcBridgeMintingParameters) { return this.#tbtcBridgeMintingParameters From a1273222598c0af269ebd6fd6ea58f79216ae49c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Mon, 18 Mar 2024 19:54:41 +0100 Subject: [PATCH 13/23] Cache `depositorFeeDivisor` This parameter will not change frequently, so we can cache this value per instance. --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 33 ++++++++++++++++---- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 7 +++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 781d1fe91..ecb403ce4 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -37,6 +37,11 @@ type TbtcBridgeMintingParameters = TbtcDepositParameters & { optimisticMintingFeeDivisor: bigint } +type BitcoinDepositorCache = { + tbtcBridgeMintingParameters: TbtcBridgeMintingParameters | undefined + depositorFeeDivisor: bigint | undefined +} + /** * Ethereum implementation of the BitcoinDepositor. */ @@ -47,7 +52,7 @@ class EthereumBitcoinDepositor extends EthersContractWrapper implements BitcoinDepositor { - #tbtcBridgeMintingParameters: TbtcBridgeMintingParameters | undefined + #cache: BitcoinDepositorCache constructor(config: EthersContractConfig, network: EthereumNetwork) { let artifact: EthersContractDeployment @@ -62,6 +67,10 @@ class EthereumBitcoinDepositor } super(config, artifact) + this.#cache = { + tbtcBridgeMintingParameters: undefined, + depositorFeeDivisor: undefined, + } } /** @@ -166,7 +175,7 @@ class EthereumBitcoinDepositor ? amountSubTreasury / optimisticMintingFeeDivisor : 0n - const depositorFeeDivisor = await this.instance.depositorFeeDivisor() + const depositorFeeDivisor = await this.#depositorFeeDivisor() // Compute depositor fee. The fee is calculated based on the initial funding // transaction amount, before the tBTC protocol network fees were taken. const depositorFee = @@ -188,8 +197,8 @@ class EthereumBitcoinDepositor // TODO: Consider exposing it from tBTC SDK. async #getTbtcBridgeMintingParameters(): Promise { - if (this.#tbtcBridgeMintingParameters) { - return this.#tbtcBridgeMintingParameters + if (this.#cache.tbtcBridgeMintingParameters) { + return this.#cache.tbtcBridgeMintingParameters } const bridgeAddress = await this.instance.bridge() @@ -206,11 +215,23 @@ class EthereumBitcoinDepositor const optimisticMintingFeeDivisor = (await vault.optimisticMintingFeeDivisor()) as bigint - this.#tbtcBridgeMintingParameters = { + this.#cache.tbtcBridgeMintingParameters = { ...depositsParameters, optimisticMintingFeeDivisor, } - return this.#tbtcBridgeMintingParameters + return this.#cache.tbtcBridgeMintingParameters + } + + async #depositorFeeDivisor(): Promise { + if (this.#cache.depositorFeeDivisor) { + return this.#cache.depositorFeeDivisor + } + + const depositorFeeDivisor = await this.instance.depositorFeeDivisor() + + this.#cache.depositorFeeDivisor = depositorFeeDivisor + + return this.#cache.depositorFeeDivisor } } diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 7157c0961..ea774c38c 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -372,6 +372,7 @@ describe("BitcoinDepositor", () => { beforeAll(async () => { mockedContractInstance.bridge.mockClear() mockedContractInstance.tbtcVault.mockClear() + mockedContractInstance.depositorFeeDivisor.mockClear() mockedBridgeContractInstance.depositsParameters.mockClear() mockedVaultContractInstance.optimisticMintingFeeDivisor.mockClear() @@ -392,6 +393,12 @@ describe("BitcoinDepositor", () => { ).toHaveBeenCalledTimes(0) }) + it("should get the bitcoin depositor fee divisor from cache", () => { + expect( + mockedContractInstance.depositorFeeDivisor, + ).toHaveBeenCalledTimes(0) + }) + it("should return correct fees", () => { expect(result2).toMatchObject(expectedResult) }) From 26a1c63641be5d614f3f7ab72339ca38a38c1690 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 12:55:28 +0200 Subject: [PATCH 14/23] Replace `staking` term with `deposit` We transition from stake to deposit naming. Here we only update places related to the estimating fees feature. We are going to update the term in a separate PR. --- sdk/src/lib/contracts/bitcoin-depositor.ts | 16 +++---- sdk/src/lib/ethereum/bitcoin-depositor.ts | 6 +-- sdk/src/modules/staking/index.ts | 24 +++++----- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 12 ++--- sdk/test/modules/staking.test.ts | 47 ++++++++++---------- sdk/test/utils/mock-acre-contracts.ts | 2 +- 6 files changed, 51 insertions(+), 56 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index e98f751d7..efd628f09 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -33,9 +33,9 @@ type TBTCMintingFees = { } /** - * Represents the Acre network staking fees. + * Represents the Acre network deposit fees. */ -type AcreStakingFees = { +type AcreDepositFees = { /** * The Acre network depositor fee taken from each Bitcoin deposit and * transferred to the treasury upon stake request finalization. @@ -43,9 +43,9 @@ type AcreStakingFees = { bitcoinDepositorFee: bigint } -export type StakingFees = { +export type DepositFees = { tbtc: TBTCMintingFees - acre: AcreStakingFees + acre: AcreDepositFees } /** @@ -77,12 +77,12 @@ export interface BitcoinDepositor extends DepositorProxy { decodeExtraData(extraData: string): DecodedExtraData /** - * Estimates the staking fees based on the provided amount. - * @param amountToStake Amount to stake in 1e8 satoshi precision. - * @returns Staking fees grouped by tBTC and Acre networks in 1e18 tBTC token + * Estimates the deposit fees based on the provided amount. + * @param amountToDeposit Amount to deposit in 1e8 satoshi precision. + * @returns Deposit fees grouped by tBTC and Acre networks in 1e18 tBTC token * precision. */ - estimateStakingFees(amountToStake: bigint): Promise + estimateDepositFees(amountToDeposit: bigint): Promise /** * @returns Minimum deposit amount. diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index cc8b3d54f..71f8915bb 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -16,7 +16,7 @@ import { DecodedExtraData, BitcoinDepositor, DepositReceipt, - StakingFees, + DepositFees, } from "../contracts" import { BitcoinRawTxVectors } from "../bitcoin" import { EthereumAddress } from "./address" @@ -162,9 +162,9 @@ class EthereumBitcoinDepositor } /** - * @see {BitcoinDepositor#estimateStakingFees} + * @see {BitcoinDepositor#estimateDepositFees} */ - async estimateStakingFees(amountToStake: bigint): Promise { + async estimateDepositFees(amountToStake: bigint): Promise { const { depositTreasuryFeeDivisor, depositTxMaxFee, diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index c178e8774..d58dc9121 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -1,17 +1,13 @@ import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" -import { - AcreContracts, - DepositorProxy, - StakingFees as StakingFeesByNetwork, -} from "../../lib/contracts" +import { AcreContracts, DepositorProxy, DepositFees } from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" import { toSatoshi } from "../../lib/utils" /** - * Represents all total staking fees grouped by network. + * Represents all total deposit fees grouped by network. */ -export type TotalStakingFees = { +export type TotalDepositFees = { tbtc: bigint acre: bigint total: bigint @@ -94,17 +90,17 @@ class StakingModule { } /** - * Estimates the staking fees based on the provided amount. - * @param amountToStake Amount to stake in satoshi. - * @returns Staking fees grouped by tBTC and Acre networks in 1e8 satoshi - * precision and total staking fees value. + * Estimates the deposit fees based on the provided amount. + * @param amount Amount to deposit in satoshi. + * @returns Deposit fees grouped by tBTC and Acre networks in 1e8 satoshi + * precision and total deposit fees value. */ - async estimateStakingFees(amount: bigint): Promise { + async estimateDepositFees(amount: bigint): Promise { const { acre: acreFees, tbtc: tbtcFees } = - await this.#contracts.bitcoinDepositor.estimateStakingFees(amount) + await this.#contracts.bitcoinDepositor.estimateDepositFees(amount) const sumFeesByNetwork = < - T extends StakingFeesByNetwork["tbtc"] | StakingFeesByNetwork["acre"], + T extends DepositFees["tbtc"] | DepositFees["acre"], >( fees: T, ) => Object.values(fees).reduce((reducer, fee) => reducer + fee, 0n) diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 55c4fdd4d..9161f858b 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -4,7 +4,7 @@ import { EthereumAddress, Hex, EthereumSigner, - StakingFees, + DepositFees, } from "../../../src" import { extraDataValidTestData } from "./data" @@ -268,7 +268,7 @@ describe("BitcoinDepositor", () => { ) }) - describe("estimateStakingFees", () => { + describe("estimateDepositFees", () => { const mockedBridgeContractInstance = { depositsParameters: jest .fn() @@ -323,10 +323,10 @@ describe("BitcoinDepositor", () => { }) describe("when network fees are not yet cached", () => { - let result: StakingFees + let result: DepositFees beforeAll(async () => { - result = await depositor.estimateStakingFees(amountToStake) + result = await depositor.estimateDepositFees(amountToStake) }) it("should get the bridge contract address", () => { @@ -375,7 +375,7 @@ describe("BitcoinDepositor", () => { }) describe("when network fees are already cached", () => { - let result2: StakingFees + let result2: DepositFees beforeAll(async () => { mockedContractInstance.bridge.mockClear() @@ -384,7 +384,7 @@ describe("BitcoinDepositor", () => { mockedBridgeContractInstance.depositsParameters.mockClear() mockedVaultContractInstance.optimisticMintingFeeDivisor.mockClear() - result2 = await depositor.estimateStakingFees(amountToStake) + result2 = await depositor.estimateDepositFees(amountToStake) }) it("should get the deposit parameters from cache", () => { diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index c960dd7e4..68172bd1c 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -8,14 +8,13 @@ import { DepositorProxy, DepositReceipt, EthereumAddress, - StakingFees, - TotalStakingFees, + DepositFees, + TotalDepositFees, } from "../../src" 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 * as satoshiConverterUtils from "../../src/lib/utils/satoshi-converter" const stakingModuleData: { initializeStake: { @@ -25,10 +24,10 @@ const stakingModuleData: { bitcoinRecoveryAddress: string mockedDepositBTCAddress: string } - estimateStakingFees: { + estimateDepositFees: { amount: bigint - mockedStakingFees: StakingFees - expectedStakingFeesInSatoshi: TotalStakingFees + mockedDepositFees: DepositFees + expectedDepositFeesInSatoshi: TotalDepositFees } } = { initializeStake: { @@ -41,9 +40,9 @@ const stakingModuleData: { mockedDepositBTCAddress: "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05", }, - estimateStakingFees: { + estimateDepositFees: { amount: 10_000_000n, // 0.1 BTC - mockedStakingFees: { + mockedDepositFees: { tbtc: { // 0.00005 tBTC in 1e18 precision. treasuryFee: 50000000000000n, @@ -57,7 +56,7 @@ const stakingModuleData: { bitcoinDepositorFee: 100000000000000n, }, }, - expectedStakingFeesInSatoshi: { + expectedDepositFeesInSatoshi: { tbtc: 124990n, acre: 10000n, total: 134990n, @@ -427,36 +426,36 @@ describe("Staking", () => { }) }) - describe("estimateStakingFees", () => { + describe("estimateDepositFees", () => { const { - estimateStakingFees: { + estimateDepositFees: { amount, - mockedStakingFees, - expectedStakingFeesInSatoshi, + mockedDepositFees, + expectedDepositFeesInSatoshi, }, } = stakingModuleData - let result: TotalStakingFees + let result: TotalDepositFees const spyOnToSatoshi = jest.spyOn(satoshiConverter, "toSatoshi") beforeAll(async () => { - contracts.bitcoinDepositor.estimateStakingFees = jest + contracts.bitcoinDepositor.estimateDepositFees = jest .fn() - .mockResolvedValue(mockedStakingFees) + .mockResolvedValue(mockedDepositFees) - result = await staking.estimateStakingFees(amount) + result = await staking.estimateDepositFees(amount) }) - it("should get the staking fees from Acre Bitcoin Depositor contract handle", () => { + it("should get the deposit fees from Acre Bitcoin Depositor contract handle", () => { expect( - contracts.bitcoinDepositor.estimateStakingFees, + contracts.bitcoinDepositor.estimateDepositFees, ).toHaveBeenCalledWith(amount) }) it("should convert tBTC network fees to satoshi", () => { const { tbtc: { depositTxMaxFee, treasuryFee, optimisticMintingFee }, - } = mockedStakingFees + } = mockedDepositFees const totalTbtcFees = depositTxMaxFee + treasuryFee + optimisticMintingFee expect(spyOnToSatoshi).toHaveBeenNthCalledWith(1, totalTbtcFees) @@ -465,20 +464,20 @@ describe("Staking", () => { it("should convert Acre network fees to satoshi", () => { const { acre: { bitcoinDepositorFee }, - } = mockedStakingFees + } = mockedDepositFees const totalAcreFees = bitcoinDepositorFee expect(spyOnToSatoshi).toHaveBeenNthCalledWith(2, totalAcreFees) }) - it("should return the staking fees in satoshi precision", () => { - expect(result).toMatchObject(expectedStakingFeesInSatoshi) + it("should return the deposit fees in satoshi precision", () => { + expect(result).toMatchObject(expectedDepositFeesInSatoshi) }) }) describe("minDepositAmount", () => { describe("should return minimum deposit amount", () => { - const spyOnToSatoshi = jest.spyOn(satoshiConverterUtils, "toSatoshi") + const spyOnToSatoshi = jest.spyOn(satoshiConverter, "toSatoshi") const mockedResult = BigInt(0.015 * 1e18) // The returned result should be in satoshi precision const expectedResult = BigInt(0.015 * 1e8) diff --git a/sdk/test/utils/mock-acre-contracts.ts b/sdk/test/utils/mock-acre-contracts.ts index 6d2ea8c1e..fe796e8f9 100644 --- a/sdk/test/utils/mock-acre-contracts.ts +++ b/sdk/test/utils/mock-acre-contracts.ts @@ -13,7 +13,7 @@ export class MockAcreContracts implements AcreContracts { decodeExtraData: jest.fn(), encodeExtraData: jest.fn(), revealDeposit: jest.fn(), - estimateStakingFees: jest.fn(), + estimateDepositFees: jest.fn(), minDepositAmount: jest.fn(), } as BitcoinDepositor From 4a454b8488faade8aa3b3269297831f1f71fce7b Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 14:16:59 +0200 Subject: [PATCH 15/23] Update deposit fees calculation Take into account stBTC deposit fee taken from each tBTC deposit to the stBTC pool which is then transferred to the treasury. --- sdk/src/lib/contracts/stbtc.ts | 8 ++++++++ sdk/src/lib/ethereum/stbtc.ts | 14 ++++++++++++++ sdk/src/modules/staking/index.ts | 8 ++++++-- sdk/test/lib/ethereum/stbtc.test.ts | 27 +++++++++++++++++++++++++++ sdk/test/modules/staking.test.ts | 22 +++++++++++++++++++--- sdk/test/utils/mock-acre-contracts.ts | 1 + 6 files changed, 75 insertions(+), 5 deletions(-) diff --git a/sdk/src/lib/contracts/stbtc.ts b/sdk/src/lib/contracts/stbtc.ts index 5eac12c4f..53c048403 100644 --- a/sdk/src/lib/contracts/stbtc.ts +++ b/sdk/src/lib/contracts/stbtc.ts @@ -12,4 +12,12 @@ export interface StBTC { * @returns Maximum withdraw value. */ assetsBalanceOf(identifier: ChainIdentifier): Promise + + /** + * Calculates the deposit fee taken from each tBTC deposit to the stBTC pool + * which is then transferred to the treasury. + * @param amount Amount to deposit in 1e18 precision. + * @returns Deposit fee. + */ + depositFee(amount: bigint): Promise } diff --git a/sdk/src/lib/ethereum/stbtc.ts b/sdk/src/lib/ethereum/stbtc.ts index d4d44610c..6adb9ec9d 100644 --- a/sdk/src/lib/ethereum/stbtc.ts +++ b/sdk/src/lib/ethereum/stbtc.ts @@ -16,6 +16,8 @@ class EthereumStBTC extends EthersContractWrapper implements StBTC { + readonly #BASIS_POINT_SCALE = BigInt(1e4) + constructor(config: EthersContractConfig, network: EthereumNetwork) { let artifact: EthersContractDeployment @@ -44,6 +46,18 @@ class EthereumStBTC assetsBalanceOf(identifier: ChainIdentifier): Promise { return this.instance.assetsBalanceOf(`0x${identifier.identifierHex}`) } + + /** + * @see {StBTC#depositFee} + */ + async depositFee(amount: bigint): Promise { + const entryFeeBasisPoints = await this.instance.entryFeeBasisPoints() + + return ( + (amount * entryFeeBasisPoints) / + (entryFeeBasisPoints + this.#BASIS_POINT_SCALE) + ) + } } // eslint-disable-next-line import/prefer-default-export diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index d58dc9121..5cbce8050 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -2,7 +2,7 @@ import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts" import { AcreContracts, DepositorProxy, DepositFees } from "../../lib/contracts" import { ChainEIP712Signer } from "../../lib/eip712-signer" import { StakeInitialization } from "./stake-initialization" -import { toSatoshi } from "../../lib/utils" +import { fromSatoshi, toSatoshi } from "../../lib/utils" /** * Represents all total deposit fees grouped by network. @@ -105,9 +105,13 @@ class StakingModule { fees: T, ) => Object.values(fees).reduce((reducer, fee) => reducer + fee, 0n) + const depositFee = await this.#contracts.stBTC.depositFee( + fromSatoshi(amount), + ) + const tbtc = toSatoshi(sumFeesByNetwork(tbtcFees)) - const acre = toSatoshi(sumFeesByNetwork(acreFees)) + const acre = toSatoshi(sumFeesByNetwork(acreFees)) + toSatoshi(depositFee) return { tbtc, diff --git a/sdk/test/lib/ethereum/stbtc.test.ts b/sdk/test/lib/ethereum/stbtc.test.ts index 4d6fa531d..898ef44b7 100644 --- a/sdk/test/lib/ethereum/stbtc.test.ts +++ b/sdk/test/lib/ethereum/stbtc.test.ts @@ -14,6 +14,7 @@ describe("stbtc", () => { const mockedContractInstance = { balanceOf: jest.fn(), assetsBalanceOf: jest.fn(), + entryFeeBasisPoints: jest.fn(), } beforeAll(() => { @@ -70,4 +71,30 @@ describe("stbtc", () => { expect(result).toEqual(expectedResult) }) }) + + describe("depositFee", () => { + // 0.1 in 1e18 precision + const amount = 100000000000000000n + const mockedEntryFeeBasisPointsValue = 1n + // (amount * basisPoints) / (basisPoints / 1e4) + const expectedResult = 9999000099990n + + let result: bigint + + beforeAll(async () => { + mockedContractInstance.entryFeeBasisPoints.mockResolvedValue( + mockedEntryFeeBasisPointsValue, + ) + + result = await stbtc.depositFee(amount) + }) + + it("should get the entry fee basis points from contract", () => { + expect(mockedContractInstance.entryFeeBasisPoints).toHaveBeenCalled() + }) + + it("should calculate the deposit fee correctly", () => { + expect(result).toEqual(expectedResult) + }) + }) }) diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index 68172bd1c..f34309bb1 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -26,7 +26,9 @@ const stakingModuleData: { } estimateDepositFees: { amount: bigint + amountIn1e18: bigint mockedDepositFees: DepositFees + stBTCDepositFee: bigint expectedDepositFeesInSatoshi: TotalDepositFees } } = { @@ -41,7 +43,9 @@ const stakingModuleData: { "tb1qma629cu92skg0t86lftyaf9uflzwhp7jk63h6mpmv3ezh6puvdhs6w2r05", }, estimateDepositFees: { - amount: 10_000_000n, // 0.1 BTC + amount: 10_000_000n, // 0.1 BTC, + // 0.1 tBTC in 1e18 precision. + amountIn1e18: 100000000000000000n, mockedDepositFees: { tbtc: { // 0.00005 tBTC in 1e18 precision. @@ -56,10 +60,12 @@ const stakingModuleData: { bitcoinDepositorFee: 100000000000000n, }, }, + // 0.001 in 1e18 precison + stBTCDepositFee: 1000000000000000n, expectedDepositFeesInSatoshi: { tbtc: 124990n, - acre: 10000n, - total: 134990n, + acre: 110000n, + total: 234990n, }, }, } @@ -430,22 +436,32 @@ describe("Staking", () => { const { estimateDepositFees: { amount, + amountIn1e18, mockedDepositFees, expectedDepositFeesInSatoshi, + stBTCDepositFee, }, } = stakingModuleData let result: TotalDepositFees const spyOnToSatoshi = jest.spyOn(satoshiConverter, "toSatoshi") + const spyOnFromSatoshi = jest.spyOn(satoshiConverter, "fromSatoshi") beforeAll(async () => { contracts.bitcoinDepositor.estimateDepositFees = jest .fn() .mockResolvedValue(mockedDepositFees) + contracts.stBTC.depositFee = jest.fn().mockResolvedValue(stBTCDepositFee) + result = await staking.estimateDepositFees(amount) }) + it("should get the stBTC deposit fee", () => { + expect(spyOnFromSatoshi).toHaveBeenNthCalledWith(1, amount) + expect(contracts.stBTC.depositFee).toHaveBeenCalledWith(amountIn1e18) + }) + it("should get the deposit fees from Acre Bitcoin Depositor contract handle", () => { expect( contracts.bitcoinDepositor.estimateDepositFees, diff --git a/sdk/test/utils/mock-acre-contracts.ts b/sdk/test/utils/mock-acre-contracts.ts index fe796e8f9..761d60f6b 100644 --- a/sdk/test/utils/mock-acre-contracts.ts +++ b/sdk/test/utils/mock-acre-contracts.ts @@ -20,6 +20,7 @@ export class MockAcreContracts implements AcreContracts { this.stBTC = { balanceOf: jest.fn(), assetsBalanceOf: jest.fn(), + depositFee: jest.fn(), } as StBTC } } From b26a8f9329d657d19e27449fbc45d733920c8989 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 14:30:59 +0200 Subject: [PATCH 16/23] Update `estimateDepositFees` fn Use a consistent precision for both, input parameters and the values the function returns. --- sdk/src/lib/contracts/bitcoin-depositor.ts | 2 +- sdk/src/lib/ethereum/bitcoin-depositor.ts | 14 +++++--------- sdk/src/modules/staking/index.ts | 13 ++++++++----- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 2 +- sdk/test/modules/staking.test.ts | 9 ++++++--- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index efd628f09..e64573f62 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -78,7 +78,7 @@ export interface BitcoinDepositor extends DepositorProxy { /** * Estimates the deposit fees based on the provided amount. - * @param amountToDeposit Amount to deposit in 1e8 satoshi precision. + * @param amountToDeposit Amount to deposit in 1e18 token precision. * @returns Deposit fees grouped by tBTC and Acre networks in 1e18 tBTC token * precision. */ diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 71f8915bb..c22a372c8 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -164,7 +164,7 @@ class EthereumBitcoinDepositor /** * @see {BitcoinDepositor#estimateDepositFees} */ - async estimateDepositFees(amountToStake: bigint): Promise { + async estimateDepositFees(amountToDeposit: bigint): Promise { const { depositTreasuryFeeDivisor, depositTxMaxFee, @@ -173,12 +173,10 @@ class EthereumBitcoinDepositor const treasuryFee = depositTreasuryFeeDivisor > 0 - ? amountToStake / depositTreasuryFeeDivisor + ? amountToDeposit / depositTreasuryFeeDivisor : 0n - // Both deposit amount and treasury fee are in the 1e8 satoshi precision. - // We need to convert them to the 1e18 TBTC precision. - const amountSubTreasury = fromSatoshi(amountToStake - treasuryFee) + const amountSubTreasury = amountToDeposit - treasuryFee const optimisticMintingFee = optimisticMintingFeeDivisor > 0 @@ -189,13 +187,11 @@ class EthereumBitcoinDepositor // Compute depositor fee. The fee is calculated based on the initial funding // transaction amount, before the tBTC protocol network fees were taken. const depositorFee = - depositorFeeDivisor > 0n - ? fromSatoshi(amountToStake) / depositorFeeDivisor - : 0n + depositorFeeDivisor > 0n ? amountToDeposit / depositorFeeDivisor : 0n return { tbtc: { - treasuryFee: fromSatoshi(treasuryFee), + treasuryFee, optimisticMintingFee, depositTxMaxFee: fromSatoshi(depositTxMaxFee), }, diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 5cbce8050..563515838 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -96,8 +96,15 @@ class StakingModule { * precision and total deposit fees value. */ async estimateDepositFees(amount: bigint): Promise { + const amountInTokenPrecision = fromSatoshi(amount) + const { acre: acreFees, tbtc: tbtcFees } = - await this.#contracts.bitcoinDepositor.estimateDepositFees(amount) + await this.#contracts.bitcoinDepositor.estimateDepositFees( + amountInTokenPrecision, + ) + const depositFee = await this.#contracts.stBTC.depositFee( + amountInTokenPrecision, + ) const sumFeesByNetwork = < T extends DepositFees["tbtc"] | DepositFees["acre"], @@ -105,10 +112,6 @@ class StakingModule { fees: T, ) => Object.values(fees).reduce((reducer, fee) => reducer + fee, 0n) - const depositFee = await this.#contracts.stBTC.depositFee( - fromSatoshi(amount), - ) - const tbtc = toSatoshi(sumFeesByNetwork(tbtcFees)) const acre = toSatoshi(sumFeesByNetwork(acreFees)) + toSatoshi(depositFee) diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index 9161f858b..c3cf80f90 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -281,7 +281,7 @@ describe("BitcoinDepositor", () => { .mockResolvedValue(testData.optimisticMintingFeeDivisor), } - const amountToStake = 10_000_000n // 0.1 BTC + const amountToStake = 100000000000000000n // 0.1 in 1e18 token precision const expectedResult = { tbtc: { diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index f34309bb1..d7eace5b4 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -457,15 +457,18 @@ describe("Staking", () => { result = await staking.estimateDepositFees(amount) }) - it("should get the stBTC deposit fee", () => { + it("should convert provided amount from satoshi to token precision", () => { expect(spyOnFromSatoshi).toHaveBeenNthCalledWith(1, amount) - expect(contracts.stBTC.depositFee).toHaveBeenCalledWith(amountIn1e18) }) it("should get the deposit fees from Acre Bitcoin Depositor contract handle", () => { expect( contracts.bitcoinDepositor.estimateDepositFees, - ).toHaveBeenCalledWith(amount) + ).toHaveBeenCalledWith(amountIn1e18) + }) + + it("should get the stBTC deposit fee", () => { + expect(contracts.stBTC.depositFee).toHaveBeenCalledWith(amountIn1e18) }) it("should convert tBTC network fees to satoshi", () => { From 0128a152322d6284a591edd6b1d4d72a8160812c Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 15:15:24 +0200 Subject: [PATCH 17/23] Fix typos in comments --- sdk/src/lib/contracts/bitcoin-depositor.ts | 12 +++++------- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index e64573f62..0a3830df8 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -25,20 +25,19 @@ type TBTCMintingFees = { */ optimisticMintingFee: bigint /** - * Maximum amount of BTC transaction fee that can - * be incurred by each swept deposit being part of the given sweep - * transaction. + * Maximum amount of BTC transaction fee that can be incurred by each swept + * deposit being part of the given sweep transaction. */ depositTxMaxFee: bigint } /** - * Represents the Acre network deposit fees. + * Represents the Acre protocol deposit fees. */ type AcreDepositFees = { /** - * The Acre network depositor fee taken from each Bitcoin deposit and - * transferred to the treasury upon stake request finalization. + * The Acre protocol depositor fee taken from each Bitcoin deposit and + * transferred to the treasury upon deposit request finalization. */ bitcoinDepositorFee: bigint } @@ -49,7 +48,6 @@ export type DepositFees = { } /** - * Interface for communication with the AcreBitcoinDepositor on-chain contract. * Interface for communication with the BitcoinDepositor on-chain contract. */ export interface BitcoinDepositor extends DepositorProxy { diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index c3cf80f90..d23d8d51d 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -19,7 +19,7 @@ const testData = { depositTreasuryFeeDivisor: 2_000n, // 1/2000 == 5bps == 0.05% == 0.0005 depositTxMaxFee: 100_000n, // 100000 satoshi = 0.001 BTC }, - optimisticMintingFeeDivisor: 500n, // 1/500 = 0.002 = 0.2%0 + optimisticMintingFeeDivisor: 500n, // 1/500 = 0.002 = 0.2% } describe("BitcoinDepositor", () => { From cd2da4bb1aa6dffd75280b6877818939ebe946ee Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 15:17:37 +0200 Subject: [PATCH 18/23] Simplify statement in `depositorFeeDivisor` fn --- sdk/src/lib/ethereum/bitcoin-depositor.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index c22a372c8..9f849e2c8 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -233,9 +233,7 @@ class EthereumBitcoinDepositor return this.#cache.depositorFeeDivisor } - const depositorFeeDivisor = await this.instance.depositorFeeDivisor() - - this.#cache.depositorFeeDivisor = depositorFeeDivisor + this.#cache.depositorFeeDivisor = await this.instance.depositorFeeDivisor() return this.#cache.depositorFeeDivisor } From 1614c5ab28aa018a0763b48d5c34041e4699ab77 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 15:22:01 +0200 Subject: [PATCH 19/23] Make function names consistent Make `BitcoinDepositor#estimateDepositFees` and `StBTC#depositFee` function names consistent. It was a little confusing to have `estimate` prefix in one but not in the other. Rename to `calculateDepositFee` in both. --- sdk/src/lib/contracts/bitcoin-depositor.ts | 4 ++-- sdk/src/lib/contracts/stbtc.ts | 2 +- sdk/src/lib/ethereum/bitcoin-depositor.ts | 4 ++-- sdk/src/lib/ethereum/stbtc.ts | 4 ++-- sdk/src/modules/staking/index.ts | 4 ++-- sdk/test/lib/ethereum/stbtc.test.ts | 2 +- sdk/test/lib/ethereum/tbtc-depositor.test.ts | 4 ++-- sdk/test/modules/staking.test.ts | 12 ++++++++---- sdk/test/utils/mock-acre-contracts.ts | 4 ++-- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/sdk/src/lib/contracts/bitcoin-depositor.ts b/sdk/src/lib/contracts/bitcoin-depositor.ts index 0a3830df8..baa71a770 100644 --- a/sdk/src/lib/contracts/bitcoin-depositor.ts +++ b/sdk/src/lib/contracts/bitcoin-depositor.ts @@ -75,12 +75,12 @@ export interface BitcoinDepositor extends DepositorProxy { decodeExtraData(extraData: string): DecodedExtraData /** - * Estimates the deposit fees based on the provided amount. + * Calculates the deposit fee based on the provided amount. * @param amountToDeposit Amount to deposit in 1e18 token precision. * @returns Deposit fees grouped by tBTC and Acre networks in 1e18 tBTC token * precision. */ - estimateDepositFees(amountToDeposit: bigint): Promise + calculateDepositFee(amountToDeposit: bigint): Promise /** * @returns Minimum deposit amount. diff --git a/sdk/src/lib/contracts/stbtc.ts b/sdk/src/lib/contracts/stbtc.ts index 53c048403..b94b71a34 100644 --- a/sdk/src/lib/contracts/stbtc.ts +++ b/sdk/src/lib/contracts/stbtc.ts @@ -19,5 +19,5 @@ export interface StBTC { * @param amount Amount to deposit in 1e18 precision. * @returns Deposit fee. */ - depositFee(amount: bigint): Promise + calculateDepositFee(amount: bigint): Promise } diff --git a/sdk/src/lib/ethereum/bitcoin-depositor.ts b/sdk/src/lib/ethereum/bitcoin-depositor.ts index 9f849e2c8..52f634c24 100644 --- a/sdk/src/lib/ethereum/bitcoin-depositor.ts +++ b/sdk/src/lib/ethereum/bitcoin-depositor.ts @@ -162,9 +162,9 @@ class EthereumBitcoinDepositor } /** - * @see {BitcoinDepositor#estimateDepositFees} + * @see {BitcoinDepositor#calculateDepositFee} */ - async estimateDepositFees(amountToDeposit: bigint): Promise { + async calculateDepositFee(amountToDeposit: bigint): Promise { const { depositTreasuryFeeDivisor, depositTxMaxFee, diff --git a/sdk/src/lib/ethereum/stbtc.ts b/sdk/src/lib/ethereum/stbtc.ts index 6adb9ec9d..94e3d0114 100644 --- a/sdk/src/lib/ethereum/stbtc.ts +++ b/sdk/src/lib/ethereum/stbtc.ts @@ -48,9 +48,9 @@ class EthereumStBTC } /** - * @see {StBTC#depositFee} + * @see {StBTC#calculateDepositFee} */ - async depositFee(amount: bigint): Promise { + async calculateDepositFee(amount: bigint): Promise { const entryFeeBasisPoints = await this.instance.entryFeeBasisPoints() return ( diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index 563515838..f1f7d5a4f 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -99,10 +99,10 @@ class StakingModule { const amountInTokenPrecision = fromSatoshi(amount) const { acre: acreFees, tbtc: tbtcFees } = - await this.#contracts.bitcoinDepositor.estimateDepositFees( + await this.#contracts.bitcoinDepositor.calculateDepositFee( amountInTokenPrecision, ) - const depositFee = await this.#contracts.stBTC.depositFee( + const depositFee = await this.#contracts.stBTC.calculateDepositFee( amountInTokenPrecision, ) diff --git a/sdk/test/lib/ethereum/stbtc.test.ts b/sdk/test/lib/ethereum/stbtc.test.ts index 898ef44b7..85b90c677 100644 --- a/sdk/test/lib/ethereum/stbtc.test.ts +++ b/sdk/test/lib/ethereum/stbtc.test.ts @@ -86,7 +86,7 @@ describe("stbtc", () => { mockedEntryFeeBasisPointsValue, ) - result = await stbtc.depositFee(amount) + result = await stbtc.calculateDepositFee(amount) }) it("should get the entry fee basis points from contract", () => { diff --git a/sdk/test/lib/ethereum/tbtc-depositor.test.ts b/sdk/test/lib/ethereum/tbtc-depositor.test.ts index d23d8d51d..882ea534a 100644 --- a/sdk/test/lib/ethereum/tbtc-depositor.test.ts +++ b/sdk/test/lib/ethereum/tbtc-depositor.test.ts @@ -326,7 +326,7 @@ describe("BitcoinDepositor", () => { let result: DepositFees beforeAll(async () => { - result = await depositor.estimateDepositFees(amountToStake) + result = await depositor.calculateDepositFee(amountToStake) }) it("should get the bridge contract address", () => { @@ -384,7 +384,7 @@ describe("BitcoinDepositor", () => { mockedBridgeContractInstance.depositsParameters.mockClear() mockedVaultContractInstance.optimisticMintingFeeDivisor.mockClear() - result2 = await depositor.estimateDepositFees(amountToStake) + result2 = await depositor.calculateDepositFee(amountToStake) }) it("should get the deposit parameters from cache", () => { diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index d7eace5b4..0d7aefd38 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -448,11 +448,13 @@ describe("Staking", () => { const spyOnFromSatoshi = jest.spyOn(satoshiConverter, "fromSatoshi") beforeAll(async () => { - contracts.bitcoinDepositor.estimateDepositFees = jest + contracts.bitcoinDepositor.calculateDepositFee = jest .fn() .mockResolvedValue(mockedDepositFees) - contracts.stBTC.depositFee = jest.fn().mockResolvedValue(stBTCDepositFee) + contracts.stBTC.calculateDepositFee = jest + .fn() + .mockResolvedValue(stBTCDepositFee) result = await staking.estimateDepositFees(amount) }) @@ -463,12 +465,14 @@ describe("Staking", () => { it("should get the deposit fees from Acre Bitcoin Depositor contract handle", () => { expect( - contracts.bitcoinDepositor.estimateDepositFees, + contracts.bitcoinDepositor.calculateDepositFee, ).toHaveBeenCalledWith(amountIn1e18) }) it("should get the stBTC deposit fee", () => { - expect(contracts.stBTC.depositFee).toHaveBeenCalledWith(amountIn1e18) + expect(contracts.stBTC.calculateDepositFee).toHaveBeenCalledWith( + amountIn1e18, + ) }) it("should convert tBTC network fees to satoshi", () => { diff --git a/sdk/test/utils/mock-acre-contracts.ts b/sdk/test/utils/mock-acre-contracts.ts index 761d60f6b..9a111cffc 100644 --- a/sdk/test/utils/mock-acre-contracts.ts +++ b/sdk/test/utils/mock-acre-contracts.ts @@ -13,14 +13,14 @@ export class MockAcreContracts implements AcreContracts { decodeExtraData: jest.fn(), encodeExtraData: jest.fn(), revealDeposit: jest.fn(), - estimateDepositFees: jest.fn(), + calculateDepositFee: jest.fn(), minDepositAmount: jest.fn(), } as BitcoinDepositor this.stBTC = { balanceOf: jest.fn(), assetsBalanceOf: jest.fn(), - depositFee: jest.fn(), + calculateDepositFee: jest.fn(), } as StBTC } } From 211bba757a00894b632917ed6d708812d7e284d4 Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 15:30:45 +0200 Subject: [PATCH 20/23] Cache the `entryFeeBasisPoints` value This value doesn't change often that's why we want to cache it per `StBTC` contract handle instance. --- sdk/src/lib/ethereum/stbtc.ts | 16 +++++++++- sdk/test/lib/ethereum/stbtc.test.ts | 46 ++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/sdk/src/lib/ethereum/stbtc.ts b/sdk/src/lib/ethereum/stbtc.ts index 94e3d0114..e1304529e 100644 --- a/sdk/src/lib/ethereum/stbtc.ts +++ b/sdk/src/lib/ethereum/stbtc.ts @@ -18,6 +18,10 @@ class EthereumStBTC { readonly #BASIS_POINT_SCALE = BigInt(1e4) + #cache: { + entryFeeBasisPoints?: bigint + } = { entryFeeBasisPoints: undefined } + constructor(config: EthersContractConfig, network: EthereumNetwork) { let artifact: EthersContractDeployment @@ -51,13 +55,23 @@ class EthereumStBTC * @see {StBTC#calculateDepositFee} */ async calculateDepositFee(amount: bigint): Promise { - const entryFeeBasisPoints = await this.instance.entryFeeBasisPoints() + const entryFeeBasisPoints = await this.#getEntryFeeBasisPoints() return ( (amount * entryFeeBasisPoints) / (entryFeeBasisPoints + this.#BASIS_POINT_SCALE) ) } + + async #getEntryFeeBasisPoints(): Promise { + if (this.#cache.entryFeeBasisPoints) { + return this.#cache.entryFeeBasisPoints + } + + this.#cache.entryFeeBasisPoints = await this.instance.entryFeeBasisPoints() + + return this.#cache.entryFeeBasisPoints + } } // eslint-disable-next-line import/prefer-default-export diff --git a/sdk/test/lib/ethereum/stbtc.test.ts b/sdk/test/lib/ethereum/stbtc.test.ts index 85b90c677..08c7dddc0 100644 --- a/sdk/test/lib/ethereum/stbtc.test.ts +++ b/sdk/test/lib/ethereum/stbtc.test.ts @@ -81,20 +81,44 @@ describe("stbtc", () => { let result: bigint - beforeAll(async () => { - mockedContractInstance.entryFeeBasisPoints.mockResolvedValue( - mockedEntryFeeBasisPointsValue, - ) - - result = await stbtc.calculateDepositFee(amount) + describe("when the entry fee basis points value is not yet cached", () => { + beforeAll(async () => { + mockedContractInstance.entryFeeBasisPoints.mockResolvedValue( + mockedEntryFeeBasisPointsValue, + ) + + result = await stbtc.calculateDepositFee(amount) + }) + + it("should get the entry fee basis points from contract", () => { + expect(mockedContractInstance.entryFeeBasisPoints).toHaveBeenCalled() + }) + + it("should calculate the deposit fee correctly", () => { + expect(result).toEqual(expectedResult) + }) }) - it("should get the entry fee basis points from contract", () => { - expect(mockedContractInstance.entryFeeBasisPoints).toHaveBeenCalled() - }) + describe("the entry fee basis points value is cached", () => { + beforeAll(async () => { + mockedContractInstance.entryFeeBasisPoints.mockResolvedValue( + mockedEntryFeeBasisPointsValue, + ) - it("should calculate the deposit fee correctly", () => { - expect(result).toEqual(expectedResult) + await stbtc.calculateDepositFee(amount) + + result = await stbtc.calculateDepositFee(amount) + }) + + it("should get the entry fee basis points from cache", () => { + expect( + mockedContractInstance.entryFeeBasisPoints, + ).toHaveBeenCalledTimes(1) + }) + + it("should calculate the deposit fee correctly", () => { + expect(result).toEqual(expectedResult) + }) }) }) }) From 1a21936b7d5dfd79ee67636280d5a93e6706ed9f Mon Sep 17 00:00:00 2001 From: Rafal Czajkowski Date: Tue, 16 Apr 2024 15:34:07 +0200 Subject: [PATCH 21/23] Rename variables/types/functions in staking module --- sdk/src/modules/staking/index.ts | 16 ++++++++-------- sdk/test/modules/staking.test.ts | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sdk/src/modules/staking/index.ts b/sdk/src/modules/staking/index.ts index f1f7d5a4f..32e8b5363 100644 --- a/sdk/src/modules/staking/index.ts +++ b/sdk/src/modules/staking/index.ts @@ -7,7 +7,7 @@ import { fromSatoshi, toSatoshi } from "../../lib/utils" /** * Represents all total deposit fees grouped by network. */ -export type TotalDepositFees = { +export type DepositFee = { tbtc: bigint acre: bigint total: bigint @@ -90,12 +90,12 @@ class StakingModule { } /** - * Estimates the deposit fees based on the provided amount. + * Estimates the deposit fee based on the provided amount. * @param amount Amount to deposit in satoshi. - * @returns Deposit fees grouped by tBTC and Acre networks in 1e8 satoshi - * precision and total deposit fees value. + * @returns Deposit fee grouped by tBTC and Acre networks in 1e8 satoshi + * precision and total deposit fee value. */ - async estimateDepositFees(amount: bigint): Promise { + async estimateDepositFee(amount: bigint): Promise { const amountInTokenPrecision = fromSatoshi(amount) const { acre: acreFees, tbtc: tbtcFees } = @@ -106,15 +106,15 @@ class StakingModule { amountInTokenPrecision, ) - const sumFeesByNetwork = < + const sumFeesByProtocol = < T extends DepositFees["tbtc"] | DepositFees["acre"], >( fees: T, ) => Object.values(fees).reduce((reducer, fee) => reducer + fee, 0n) - const tbtc = toSatoshi(sumFeesByNetwork(tbtcFees)) + const tbtc = toSatoshi(sumFeesByProtocol(tbtcFees)) - const acre = toSatoshi(sumFeesByNetwork(acreFees)) + toSatoshi(depositFee) + const acre = toSatoshi(sumFeesByProtocol(acreFees)) + toSatoshi(depositFee) return { tbtc, diff --git a/sdk/test/modules/staking.test.ts b/sdk/test/modules/staking.test.ts index 0d7aefd38..81909295e 100644 --- a/sdk/test/modules/staking.test.ts +++ b/sdk/test/modules/staking.test.ts @@ -9,7 +9,7 @@ import { DepositReceipt, EthereumAddress, DepositFees, - TotalDepositFees, + DepositFee, } from "../../src" import * as satoshiConverter from "../../src/lib/utils/satoshi-converter" import { MockAcreContracts } from "../utils/mock-acre-contracts" @@ -29,7 +29,7 @@ const stakingModuleData: { amountIn1e18: bigint mockedDepositFees: DepositFees stBTCDepositFee: bigint - expectedDepositFeesInSatoshi: TotalDepositFees + expectedDepositFeesInSatoshi: DepositFee } } = { initializeStake: { @@ -443,7 +443,7 @@ describe("Staking", () => { }, } = stakingModuleData - let result: TotalDepositFees + let result: DepositFee const spyOnToSatoshi = jest.spyOn(satoshiConverter, "toSatoshi") const spyOnFromSatoshi = jest.spyOn(satoshiConverter, "fromSatoshi") @@ -456,7 +456,7 @@ describe("Staking", () => { .fn() .mockResolvedValue(stBTCDepositFee) - result = await staking.estimateDepositFees(amount) + result = await staking.estimateDepositFee(amount) }) it("should convert provided amount from satoshi to token precision", () => { From fb180ef173c77f791cd0c0c68232c93db76ed6d6 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Thu, 18 Apr 2024 13:35:13 +0200 Subject: [PATCH 22/23] Implement page component Added new route, updated route paths --- dapp/src/pages/ActivityPage/index.tsx | 3 ++- dapp/src/pages/LandingPage/index.tsx | 10 ++++++++++ dapp/src/router/index.tsx | 5 +++++ dapp/src/router/path.ts | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 dapp/src/pages/LandingPage/index.tsx diff --git a/dapp/src/pages/ActivityPage/index.tsx b/dapp/src/pages/ActivityPage/index.tsx index 8d61143c2..dc8695363 100644 --- a/dapp/src/pages/ActivityPage/index.tsx +++ b/dapp/src/pages/ActivityPage/index.tsx @@ -3,6 +3,7 @@ import { Flex, Link as ChakraLink, Icon } from "@chakra-ui/react" import { Link as ReactRouterLink } from "react-router-dom" import { useSidebar } from "#/hooks" +import { routerPath } from "#/router/path" import { ArrowLeft } from "#/assets/icons" import ActivityDetails from "./ActivityDetails" import { ActivityBar } from "./ActivityBar" @@ -17,7 +18,7 @@ export default function ActivityPage() { return ( - + + Content + + ) +} diff --git a/dapp/src/router/index.tsx b/dapp/src/router/index.tsx index 5d280ad22..24c5e6bd5 100644 --- a/dapp/src/router/index.tsx +++ b/dapp/src/router/index.tsx @@ -1,5 +1,6 @@ import React from "react" import { createBrowserRouter } from "react-router-dom" +import LandingPage from "#/pages/LandingPage" import OverviewPage from "#/pages/OverviewPage" import ActivityPage from "#/pages/ActivityPage" import { routerPath } from "./path" @@ -7,6 +8,10 @@ import { routerPath } from "./path" export const router = createBrowserRouter([ { path: routerPath.home, + element: , + }, + { + path: routerPath.overview, element: , }, { diff --git a/dapp/src/router/path.ts b/dapp/src/router/path.ts index be0fbef7c..478cd0efc 100644 --- a/dapp/src/router/path.ts +++ b/dapp/src/router/path.ts @@ -1,4 +1,5 @@ export const routerPath = { home: "/", + overview: "/overview", activity: "/activity-details", } From a165f04e873003a0da498fca1ef5c44cc1bc9448 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Thu, 18 Apr 2024 14:10:47 +0200 Subject: [PATCH 23/23] Define home route as index --- dapp/src/router/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/dapp/src/router/index.tsx b/dapp/src/router/index.tsx index 24c5e6bd5..0b3243eb7 100644 --- a/dapp/src/router/index.tsx +++ b/dapp/src/router/index.tsx @@ -9,6 +9,7 @@ export const router = createBrowserRouter([ { path: routerPath.home, element: , + index: true, }, { path: routerPath.overview,