pool/order/tradingfees.go
2020-09-09 19:55:10 -07:00

240 lines
9 KiB
Go

package order
import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wallet/txrules"
"github.com/lightninglabs/pool/poolscript"
"github.com/lightninglabs/pool/terms"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
var (
// FeeRateTotalParts defines the granularity of the fixed rate used to
// compute the per-block interest rate. Throughout the codebase, we'll
// use fix based arithmetic to compute fees.
FeeRateTotalParts = 1e9
// dustLimitP2WPKH is the minimum size of a P2WPKH output to not be
// considered dust.
dustLimitP2WPKH = txrules.GetDustThreshold(
input.P2WPKHSize, txrules.DefaultRelayFeePerKb,
)
// MinNoDustAccountSize is the minimum number of satoshis an account
// output value needs to be to not be considered a dust output. This is
// the cost of a spend TX at 1 sat/byte plus the minimum non-dust output
// size.
MinNoDustAccountSize = minNoDustAccountSize()
)
// FixedRatePremium is the unit that we'll use to express the "lease" rate of
// the funds within a channel. This value is compounded every N blocks. As a
// result, a trader will pay for the longer they wish to allocate liquidity to
// another agent in the market. In other words, this is our period interest
// rate.
type FixedRatePremium uint32
// LumpSumPremium calculates the total amount that will be paid out to lease an
// asset at the target FixedRatePremium, for the specified amount. This
// computes the total amount that will be paid over the lifetime of the asset.
// We'll use this to compute the total amount that a taker needs to set aside
// once they're matched.
func (f FixedRatePremium) LumpSumPremium(amt btcutil.Amount,
durationBlocks uint32) btcutil.Amount {
// First, we'll compute the premium that will be paid each block over
// the lifetime of the asset. This can be a fraction of a satoshi as one
// block is a very short period.
premiumPerBlock := PerBlockPremium(amt, uint32(f))
// Once we have this value, we can then multiply the premium paid per
// block times the number of compounding periods, or the total lease
// duration.
return btcutil.Amount(premiumPerBlock * float64(durationBlocks))
}
// PerBlockPremium calculates the absolute premium in fractions of satoshis for
// a one block duration from the amount and the specified fee rate in parts per
// million.
func PerBlockPremium(amt btcutil.Amount, fixedRate uint32) float64 {
return float64(amt) * float64(fixedRate) / FeeRateTotalParts
}
// EstimateTraderFee calculates the chain fees a trader has to pay for their
// part of a batch transaction. The more outputs a trader creates (channels),
// the higher fee they will pay.
func EstimateTraderFee(numTraderChans uint32,
feeRate chainfee.SatPerKWeight) btcutil.Amount {
var weightEstimate int64
// First we'll tack on the size of their account output that will be
// threaded through in this batch.
weightEstimate += input.P2WSHOutputSize
// Next we'll add the size of a typical input to account for the input
// spending their account outpoint.
weightEstimate += input.InputSize
// Next, for each channel that will be created involving this trader,
// we'll add the size of a regular P2WSH output. We divide value by two
// as the maker and taker will split this fees.
chanOutputSize := uint32(input.P2WSHOutputSize)
weightEstimate += int64(chanOutputSize*numTraderChans+1) / 2
// At this point we've tallied all the non-witness data, so we multiply
// by 4 to scale it up.
weightEstimate *= blockchain.WitnessScaleFactor
// Finally, we tack on the size of the witness spending the account
// outpoint.
weightEstimate += poolscript.MultiSigWitnessSize
return feeRate.FeeForWeight(weightEstimate)
}
// AccountTally keeps track of an account's balance and fees for all orders in
// a batch that spend from/use that account.
type AccountTally struct {
// EndingBalance is the ending balance for a trader's account.
EndingBalance btcutil.Amount
// TotalExecutionFeesPaid is the total amount of fees a trader paid to
// the venue.
TotalExecutionFeesPaid btcutil.Amount
// TotalTakerFeesPaid is the total amount of fees the trader paid to
// purchase any channels in this batch.
TotalTakerFeesPaid btcutil.Amount
// TotalMakerFeesAccrued is the total amount of fees the trader gained
// by selling channels in this batch.
TotalMakerFeesAccrued btcutil.Amount
// NumChansCreated is the number of new channels that were created for
// one account in a batch. This is needed to calculate the chain fees
// that need to be paid from that account.
NumChansCreated uint32
}
// makerDelta calculates an account's balance and fee difference for a single
// order where the account is involved on the maker side. It returns the
// balance delta, fees accrued, and execution fee paid.
func makerDelta(feeSchedule terms.FeeSchedule, price FixedRatePremium,
totalSats btcutil.Amount, duration uint32) (btcutil.Amount,
btcutil.Amount, btcutil.Amount) {
// First, we'll need to subtract the total amount of sats cleared (the
// channel size), from the balance of the maker.
balanceDelta := -totalSats
// Calculate the premium based on the duration the capital will be
// locked for.
satsPremium := price.LumpSumPremium(totalSats, duration)
// The premium is paid to the maker.
balanceDelta += satsPremium
makerFeesAccrued := satsPremium
// Finally, we'll subtract the paid execution fees from the balance.
executionFee := executionFee(totalSats, feeSchedule)
balanceDelta -= executionFee
return balanceDelta, makerFeesAccrued, executionFee
}
// CalcMakerDelta calculates an account's balance and fee difference for a
// single order where the account is involved on the maker side. It returns the
// execution fee that is collected by the auctioneer.
func (t *AccountTally) CalcMakerDelta(feeSchedule terms.FeeSchedule,
price FixedRatePremium, totalSats btcutil.Amount,
duration uint32) btcutil.Amount {
// Calculate the deltas and update the tally.
balanceDelta, makerFeesAccrued, executionFee := makerDelta(
feeSchedule, price, totalSats, duration,
)
t.EndingBalance += balanceDelta
t.TotalMakerFeesAccrued += makerFeesAccrued
t.TotalExecutionFeesPaid += executionFee
return executionFee
}
// takerDelta calculates an account's balance and fee difference for a single
// order where the account is involved on the taker side. It returns the
// balance delta, taker fees paid, and execution fee paid.
func takerDelta(feeSchedule terms.FeeSchedule,
price FixedRatePremium, totalSats btcutil.Amount,
duration uint32) (btcutil.Amount, btcutil.Amount, btcutil.Amount) {
// Calculate the premium based on the duration the capital will be
// locked for.
satsPremium := price.LumpSumPremium(totalSats, duration)
// The premium must be paid by the taker.
balanceDelta := -satsPremium
takerFeesPaid := satsPremium
// Finally, we'll subtract the paid execution fees from the balance.
executionFee := executionFee(totalSats, feeSchedule)
balanceDelta -= executionFee
return balanceDelta, takerFeesPaid, executionFee
}
// CalcTakerDelta calculates an account's balance and fee difference for a
// single order where the account is involved on the taker side. It returns the
// execution fee that is collected by the auctioneer.
func (t *AccountTally) CalcTakerDelta(feeSchedule terms.FeeSchedule,
price FixedRatePremium, totalSats btcutil.Amount,
duration uint32) btcutil.Amount {
// Calculate the deltas and update the tally.
balanceDelta, takerFeesPaid, executionFee := takerDelta(
feeSchedule, price, totalSats, duration,
)
t.EndingBalance += balanceDelta
t.TotalTakerFeesPaid += takerFeesPaid
t.TotalExecutionFeesPaid += executionFee
return executionFee
}
// ChainFees estimates the chain fees that need to be paid for the number of
// channels created for this account and subtracts that value from the ending
// balance.
func (t *AccountTally) ChainFees(feeRate chainfee.SatPerKWeight) {
chainFeesDue := EstimateTraderFee(t.NumChansCreated, feeRate)
t.EndingBalance -= chainFeesDue
}
// minNoDustAccountSize returns the minimum number of satoshis an account output
// value needs to be to not be considered a dust output. This is the cost of a
// spend TX at 1 sat/byte plus the minimum non-dust output size.
func minNoDustAccountSize() btcutil.Amount {
// Calculate the minimum fee we would need to pay to coop close the
// account to a P2WKH output.
var weightEstimator input.TxWeightEstimator
weightEstimator.AddWitnessInput(poolscript.MultiSigWitnessSize)
weightEstimator.AddP2WKHOutput()
minimumFee := chainfee.FeePerKwFloor.FeeForWeight(
int64(weightEstimator.Weight()),
)
// After paying the fee, more than dust needs to remain, otherwise it
// doesn't make sense to sweep the account.
return minimumFee + dustLimitP2WPKH
}
// executionFee calculates the execution fee which is the base fee plus the
// execution fee which scales based on the order size.
func executionFee(amount btcutil.Amount,
schedule terms.FeeSchedule) btcutil.Amount {
return schedule.BaseFee() + schedule.ExecutionFee(amount)
}