Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Estimate staking fees #281

Merged
merged 24 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2b3a1a3
Add `depositorFeeDivisor` to Depositor contract
r-czajkowski Feb 29, 2024
61dc761
Estimate staking fees
r-czajkowski Feb 29, 2024
7bbdcf8
Expose fee breakdown for staking operation
r-czajkowski Feb 29, 2024
57fdf57
Merge branch 'main' into sdk-estimate-staking-fees
r-czajkowski Mar 14, 2024
166dac9
Group the staking fees by networks
r-czajkowski Mar 14, 2024
b567014
Expose the `estimateStakingFees` in staking module
r-czajkowski Mar 14, 2024
ac1c50b
Update `estimateStakingFees`
r-czajkowski Mar 14, 2024
6574179
Update `estimateStakingFees` Ethereum function
r-czajkowski Mar 14, 2024
de56282
Update `estimateStakingFees` in staking module
r-czajkowski Mar 18, 2024
554c75e
Merge branch 'main' into sdk-estimate-staking-fees
r-czajkowski Mar 18, 2024
59c15b8
Rename field in `AcreStakingFees` type
r-czajkowski Mar 18, 2024
c706f88
Combine all tBTC Bridge params in one obj and fn
r-czajkowski Mar 18, 2024
a743b88
Add `fromSatoshi` utils function
r-czajkowski Mar 18, 2024
fb089a5
Leave `TODO`
r-czajkowski Mar 18, 2024
a127322
Cache `depositorFeeDivisor`
r-czajkowski Mar 18, 2024
013880d
Merge branch 'main' into sdk-estimate-staking-fees
r-czajkowski Apr 16, 2024
26a1c63
Replace `staking` term with `deposit`
r-czajkowski Apr 16, 2024
4a454b8
Update deposit fees calculation
r-czajkowski Apr 16, 2024
b26a8f9
Update `estimateDepositFees` fn
r-czajkowski Apr 16, 2024
0128a15
Fix typos in comments
r-czajkowski Apr 16, 2024
cd2da4b
Simplify statement in `depositorFeeDivisor` fn
r-czajkowski Apr 16, 2024
1614c5a
Make function names consistent
r-czajkowski Apr 16, 2024
211bba7
Cache the `entryFeeBasisPoints` value
r-czajkowski Apr 16, 2024
1a21936
Rename variables/types/functions in staking module
r-czajkowski Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions sdk/src/lib/contracts/bitcoin-depositor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,46 @@ export type DecodedExtraData = {
}

/**
* 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
* 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.
nkuba marked this conversation as resolved.
Show resolved Hide resolved
*/
depositTxMaxFee: bigint
}

/**
* Represents the Acre network deposit fees.
nkuba marked this conversation as resolved.
Show resolved Hide resolved
*/
type AcreDepositFees = {
/**
* The Acre network depositor fee taken from each Bitcoin deposit and
* transferred to the treasury upon stake request finalization.
nkuba marked this conversation as resolved.
Show resolved Hide resolved
*/
bitcoinDepositorFee: bigint
}

export type DepositFees = {
tbtc: TBTCMintingFees
acre: AcreDepositFees
}

/**
* Interface for communication with the AcreBitcoinDepositor on-chain contract.
* Interface for communication with the BitcoinDepositor on-chain contract.
nkuba marked this conversation as resolved.
Show resolved Hide resolved
*/
export interface BitcoinDepositor extends DepositorProxy {
Expand All @@ -36,6 +76,14 @@ export interface BitcoinDepositor extends DepositorProxy {
*/
decodeExtraData(extraData: string): DecodedExtraData

/**
* Estimates the deposit fees 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<DepositFees>
nkuba marked this conversation as resolved.
Show resolved Hide resolved

/**
* @returns Minimum deposit amount.
*/
Expand Down
8 changes: 8 additions & 0 deletions sdk/src/lib/contracts/stbtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,12 @@ export interface StBTC {
* @returns Maximum withdraw value.
*/
assetsBalanceOf(identifier: ChainIdentifier): Promise<bigint>

/**
* 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<bigint>
}
103 changes: 102 additions & 1 deletion sdk/src/lib/ethereum/bitcoin-depositor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import {
isAddress,
solidityPacked,
zeroPadBytes,
Contract,
} from "ethers"
import {
ChainIdentifier,
DecodedExtraData,
BitcoinDepositor,
DepositReceipt,
DepositFees,
} from "../contracts"
import { BitcoinRawTxVectors } from "../bitcoin"
import { EthereumAddress } from "./address"
Expand All @@ -23,9 +25,23 @@ import {
EthersContractDeployment,
EthersContractWrapper,
} from "./contract"
import { Hex } from "../utils"
import { Hex, fromSatoshi } from "../utils"
import { EthereumNetwork } from "./network"

type TbtcDepositParameters = {
depositTreasuryFeeDivisor: bigint
depositTxMaxFee: bigint
}
nkuba marked this conversation as resolved.
Show resolved Hide resolved

type TbtcBridgeMintingParameters = TbtcDepositParameters & {
optimisticMintingFeeDivisor: bigint
}

type BitcoinDepositorCache = {
tbtcBridgeMintingParameters: TbtcBridgeMintingParameters | undefined
depositorFeeDivisor: bigint | undefined
}

/**
* Ethereum implementation of the BitcoinDepositor.
*/
Expand All @@ -36,6 +52,8 @@ class EthereumBitcoinDepositor
extends EthersContractWrapper<BitcoinDepositorTypechain>
implements BitcoinDepositor
{
#cache: BitcoinDepositorCache

constructor(config: EthersContractConfig, network: EthereumNetwork) {
let artifact: EthersContractDeployment

Expand All @@ -49,6 +67,10 @@ class EthereumBitcoinDepositor
}

super(config, artifact)
this.#cache = {
tbtcBridgeMintingParameters: undefined,
depositorFeeDivisor: undefined,
}
}

/**
Expand Down Expand Up @@ -138,6 +160,85 @@ class EthereumBitcoinDepositor
async minDepositAmount(): Promise<bigint> {
return this.instance.minDepositAmount()
}

/**
* @see {BitcoinDepositor#estimateDepositFees}
*/
async estimateDepositFees(amountToDeposit: bigint): Promise<DepositFees> {
const {
depositTreasuryFeeDivisor,
depositTxMaxFee,
optimisticMintingFeeDivisor,
} = await this.#getTbtcBridgeMintingParameters()

const treasuryFee =
depositTreasuryFeeDivisor > 0
? amountToDeposit / depositTreasuryFeeDivisor
: 0n

const amountSubTreasury = amountToDeposit - treasuryFee

const optimisticMintingFee =
optimisticMintingFeeDivisor > 0
? amountSubTreasury / optimisticMintingFeeDivisor
: 0n

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 =
depositorFeeDivisor > 0n ? amountToDeposit / depositorFeeDivisor : 0n

return {
tbtc: {
treasuryFee,
optimisticMintingFee,
depositTxMaxFee: fromSatoshi(depositTxMaxFee),
},
acre: {
bitcoinDepositorFee: depositorFee,
},
}
}

// TODO: Consider exposing it from tBTC SDK.
async #getTbtcBridgeMintingParameters(): Promise<TbtcBridgeMintingParameters> {
if (this.#cache.tbtcBridgeMintingParameters) {
return this.#cache.tbtcBridgeMintingParameters
}

const bridgeAddress = await this.instance.bridge()
const bridge = new Contract(bridgeAddress, [
"function depositsParameters()",
])
const depositsParameters =
(await bridge.depositsParameters()) as TbtcDepositParameters

const vaultAddress = await this.getTbtcVaultChainIdentifier()
const vault = new Contract(`0x${vaultAddress.identifierHex}`, [
"function optimisticMintingFeeDivisor()",
])
const optimisticMintingFeeDivisor =
(await vault.optimisticMintingFeeDivisor()) as bigint

this.#cache.tbtcBridgeMintingParameters = {
...depositsParameters,
optimisticMintingFeeDivisor,
}
return this.#cache.tbtcBridgeMintingParameters
}

async #depositorFeeDivisor(): Promise<bigint> {
if (this.#cache.depositorFeeDivisor) {
return this.#cache.depositorFeeDivisor
}

const depositorFeeDivisor = await this.instance.depositorFeeDivisor()

this.#cache.depositorFeeDivisor = depositorFeeDivisor
nkuba marked this conversation as resolved.
Show resolved Hide resolved

return this.#cache.depositorFeeDivisor
}
}

export { EthereumBitcoinDepositor, packRevealDepositParameters }
14 changes: 14 additions & 0 deletions sdk/src/lib/ethereum/stbtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class EthereumStBTC
extends EthersContractWrapper<StBTCTypechain>
implements StBTC
{
readonly #BASIS_POINT_SCALE = BigInt(1e4)

constructor(config: EthersContractConfig, network: EthereumNetwork) {
let artifact: EthersContractDeployment

Expand Down Expand Up @@ -44,6 +46,18 @@ class EthereumStBTC
assetsBalanceOf(identifier: ChainIdentifier): Promise<bigint> {
return this.instance.assetsBalanceOf(`0x${identifier.identifierHex}`)
}

/**
* @see {StBTC#depositFee}
*/
async depositFee(amount: bigint): Promise<bigint> {
const entryFeeBasisPoints = await this.instance.entryFeeBasisPoints()
nkuba marked this conversation as resolved.
Show resolved Hide resolved

return (
(amount * entryFeeBasisPoints) /
(entryFeeBasisPoints + this.#BASIS_POINT_SCALE)
)
}
}

// eslint-disable-next-line import/prefer-default-export
Expand Down
47 changes: 45 additions & 2 deletions sdk/src/modules/staking/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { ChainIdentifier, TBTC } from "@keep-network/tbtc-v2.ts"
import { AcreContracts, DepositorProxy } 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"
import { fromSatoshi, toSatoshi } from "../../lib/utils"

/**
* Represents all total deposit fees grouped by network.
*/
export type TotalDepositFees = {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
tbtc: bigint
acre: bigint
total: bigint
}

/**
* Module exposing features related to the staking.
Expand Down Expand Up @@ -80,6 +89,40 @@ class StakingModule {
return this.#contracts.stBTC.assetsBalanceOf(identifier)
}

/**
* 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 estimateDepositFees(amount: bigint): Promise<TotalDepositFees> {
nkuba marked this conversation as resolved.
Show resolved Hide resolved
const amountInTokenPrecision = fromSatoshi(amount)

const { acre: acreFees, tbtc: tbtcFees } =
await this.#contracts.bitcoinDepositor.estimateDepositFees(
amountInTokenPrecision,
)
const depositFee = await this.#contracts.stBTC.depositFee(
amountInTokenPrecision,
)

const sumFeesByNetwork = <
nkuba marked this conversation as resolved.
Show resolved Hide resolved
T extends DepositFees["tbtc"] | DepositFees["acre"],
>(
fees: T,
) => Object.values(fees).reduce((reducer, fee) => reducer + fee, 0n)

const tbtc = toSatoshi(sumFeesByNetwork(tbtcFees))

const acre = toSatoshi(sumFeesByNetwork(acreFees)) + toSatoshi(depositFee)

return {
tbtc,
acre,
total: tbtc + acre,
}
}

/**
* @returns Minimum deposit amount in 1e8 satoshi precision.
*/
Expand Down
27 changes: 27 additions & 0 deletions sdk/test/lib/ethereum/stbtc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ describe("stbtc", () => {
const mockedContractInstance = {
balanceOf: jest.fn(),
assetsBalanceOf: jest.fn(),
entryFeeBasisPoints: jest.fn(),
}

beforeAll(() => {
Expand Down Expand Up @@ -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)
})
})
})
Loading
Loading