Skip to content

Commit

Permalink
Merc-1640 fixing Mercury fee calculation (#168)
Browse files Browse the repository at this point in the history
* Fixing native/link fee calculation

* Inclusing cents_in_usd into the price_scaling_factor to simplify the fee calculation

* CalculateFee returns 0 when token price or base fee is 0

* Using decimal package for fee calculation

* Fixing tests
  • Loading branch information
sdrug authored Sep 8, 2023
1 parent e2197ee commit e2f9fcf
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 36 deletions.
41 changes: 28 additions & 13 deletions pkg/reportingplugins/mercury/fees.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
package mercury

import "math/big"
import (
"math/big"

// PriceScalingFactor indicates the multiplier applied to token prices.
"github.com/shopspring/decimal"
)

// PriceScalingFactor indicates the multiplier applied to token prices that we expect from data source
// e.g. for a 1e8 multiplier, a LINK/USD value of 7.42 will be represented as 742000000
// This is what we expect from our data source.
var PRICE_SCALING_FACTOR = big.NewInt(1e8)
// The factor is decreased 1e8 -> 1e6 to comnpensate for baseUSDFee being in cents not usd
var PRICE_SCALING_FACTOR = decimal.NewFromInt(1e6)

// FeeScalingFactor indicates the multiplier applied to fees.
// e.g. for a 1e18 multiplier, a LINK fee of 7.42 will be represented as 7.42e18
// This is what will be baked into the report for use on-chain.
var FEE_SCALING_FACTOR = big.NewInt(1e18)
var FEE_SCALING_FACTOR = decimal.NewFromInt(1e18)

// CalculateFee outputs a fee in wei according to the formula: baseUSDFeeCents * scaleFactor / tokenPriceInUSD
func CalculateFee(tokenPriceInUSD *big.Int, baseUSDFeeCents uint32) *big.Int {
if tokenPriceInUSD.Cmp(big.NewInt(0)) == 0 || baseUSDFeeCents == 0 {
// zero fee if token price or base fee is zero
return big.NewInt(0)
}

// scale baseFee in USD
baseFeeScaled := decimal.NewFromInt32(int32(baseUSDFeeCents)).Mul(PRICE_SCALING_FACTOR)

tokenPrice := decimal.NewFromBigInt(tokenPriceInUSD, 0)

// fee denominated in token
fee := baseFeeScaled.Div(tokenPrice)

var CENTS_PER_DOLLAR = big.NewInt(100)
// scale fee to the expected format
fee = fee.Mul(FEE_SCALING_FACTOR)

// CalculateFee outputs a fee in wei
func CalculateFee(tokenPriceInUSD *big.Int, baseUSDFeeCents uint32) (fee *big.Int) {
fee = new(big.Int).Mul(big.NewInt(int64(baseUSDFeeCents)), tokenPriceInUSD)
fee = fee.Mul(fee, FEE_SCALING_FACTOR)
fee = fee.Div(fee, PRICE_SCALING_FACTOR)
fee = fee.Div(fee, CENTS_PER_DOLLAR)
return
// convert to big.Int
return fee.BigInt()
}
41 changes: 36 additions & 5 deletions pkg/reportingplugins/mercury/fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,46 @@ package mercury
import (
"math/big"
"testing"

"github.com/stretchr/testify/assert"
)

func scalePrice(usdPrice float64) *big.Int {
scaledPrice := new(big.Float).Mul(big.NewFloat(usdPrice), big.NewFloat(1e8))
scaledPriceInt, _ := scaledPrice.Int(nil)
return scaledPriceInt
}

func Test_Fees(t *testing.T) {
t.Run("CalculateFee", func(t *testing.T) {
tokenPriceInUSD := big.NewInt(655000000)
var baseUSDFeeCents uint32 = 100
var baseUSDFeeCents uint32 = 70
t.Run("with token price > 1", func(t *testing.T) {
tokenPriceInUSD := scalePrice(1630)
fee := CalculateFee(tokenPriceInUSD, baseUSDFeeCents)
expectedFee := big.NewInt(429447852760700) // 0.0004294478527607 18 decimals
if fee.Cmp(expectedFee) != 0 {
t.Errorf("Expected fee to be %v, got %v", expectedFee, fee)
}
})

t.Run("with token price < 1", func(t *testing.T) {
tokenPriceInUSD := scalePrice(0.4)
fee := CalculateFee(tokenPriceInUSD, baseUSDFeeCents)
if fee.Cmp(big.NewInt(6.55e18)) != 0 {
t.Errorf("Expected fee to be 6550000000000000000, got %v", fee)
expectedFee := big.NewInt(1750000000000000000) // 1.75 18 decimals
if fee.Cmp(expectedFee) != 0 {
t.Errorf("Expected fee to be %v, got %v", expectedFee, fee)
}
})

t.Run("with token price == 0", func(t *testing.T) {
tokenPriceInUSD := scalePrice(0)
fee := CalculateFee(tokenPriceInUSD, baseUSDFeeCents)
assert.Equal(t, big.NewInt(0), fee)
})

t.Run("with base fee == 0", func(t *testing.T) {
tokenPriceInUSD := scalePrice(123)
baseUSDFeeCents = 0
fee := CalculateFee(tokenPriceInUSD, baseUSDFeeCents)
assert.Equal(t, big.NewInt(0), fee)
})
}
19 changes: 10 additions & 9 deletions pkg/reportingplugins/mercury/v2/mercury_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,13 @@ func Test_Plugin_Observation(t *testing.T) {
assert.True(t, p.PricesValid)
assert.Equal(t, obs.MaxFinalizedTimestamp.Val, p.MaxFinalizedTimestamp)
assert.True(t, p.MaxFinalizedTimestampValid)
assert.Equal(t, mercury.CalculateFee(obs.LinkPrice.Val, 100), mustDecodeBigInt(p.LinkFee))

fee := mercury.CalculateFee(obs.LinkPrice.Val, 100)
assert.Equal(t, fee, mustDecodeBigInt(p.LinkFee))
assert.True(t, p.LinkFeeValid)
assert.Equal(t, mercury.CalculateFee(obs.NativePrice.Val, 100), mustDecodeBigInt(p.NativeFee))

fee = mercury.CalculateFee(obs.NativePrice.Val, 100)
assert.Equal(t, fee, mustDecodeBigInt(p.NativeFee))
assert.True(t, p.NativeFeeValid)
})

Expand Down Expand Up @@ -520,7 +524,9 @@ func Test_Plugin_Observation(t *testing.T) {
assert.False(t, p.MaxFinalizedTimestampValid)
assert.Zero(t, p.LinkFee)
assert.False(t, p.LinkFeeValid)
assert.Equal(t, mercury.CalculateFee(obs.NativePrice.Val, 100), mustDecodeBigInt(p.NativeFee))

fee := mercury.CalculateFee(obs.NativePrice.Val, 100)
assert.Equal(t, fee, mustDecodeBigInt(p.NativeFee))
assert.True(t, p.NativeFeeValid)
})

Expand Down Expand Up @@ -585,8 +591,6 @@ func Test_Plugin_Observation(t *testing.T) {

assert.Zero(t, p.BenchmarkPrice)
assert.False(t, p.PricesValid)
assert.Zero(t, p.LinkFee)
assert.False(t, p.LinkFeeValid)
})

t.Run("encoding fails on all observations", func(t *testing.T) {
Expand All @@ -597,6 +601,7 @@ func Test_Plugin_Observation(t *testing.T) {
MaxFinalizedTimestamp: mercury.ObsResult[int64]{
Val: rand.Int63(),
},
// encoding never fails on calculated fees
LinkPrice: mercury.ObsResult[*big.Int]{
Val: new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil),
},
Expand All @@ -615,10 +620,6 @@ func Test_Plugin_Observation(t *testing.T) {

assert.Zero(t, p.BenchmarkPrice)
assert.False(t, p.PricesValid)
assert.Zero(t, p.LinkFee)
assert.False(t, p.LinkFeeValid)
assert.Zero(t, p.NativeFee)
assert.False(t, p.NativeFeeValid)
})
}

Expand Down
19 changes: 10 additions & 9 deletions pkg/reportingplugins/mercury/v3/mercury_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,9 +494,13 @@ func Test_Plugin_Observation(t *testing.T) {
assert.True(t, p.PricesValid)
assert.Equal(t, obs.MaxFinalizedTimestamp.Val, p.MaxFinalizedTimestamp)
assert.True(t, p.MaxFinalizedTimestampValid)
assert.Equal(t, mercury.CalculateFee(obs.LinkPrice.Val, 100), mustDecodeBigInt(p.LinkFee))

fee := mercury.CalculateFee(obs.LinkPrice.Val, 100)
assert.Equal(t, fee, mustDecodeBigInt(p.LinkFee))
assert.True(t, p.LinkFeeValid)
assert.Equal(t, mercury.CalculateFee(obs.NativePrice.Val, 100), mustDecodeBigInt(p.NativeFee))

fee = mercury.CalculateFee(obs.NativePrice.Val, 100)
assert.Equal(t, fee, mustDecodeBigInt(p.NativeFee))
assert.True(t, p.NativeFeeValid)
})

Expand Down Expand Up @@ -557,7 +561,9 @@ func Test_Plugin_Observation(t *testing.T) {
assert.False(t, p.MaxFinalizedTimestampValid)
assert.Zero(t, p.LinkFee)
assert.False(t, p.LinkFeeValid)
assert.Equal(t, mercury.CalculateFee(obs.NativePrice.Val, 100), mustDecodeBigInt(p.NativeFee))

fee := mercury.CalculateFee(obs.NativePrice.Val, 100)
assert.Equal(t, fee, mustDecodeBigInt(p.NativeFee))
assert.True(t, p.NativeFeeValid)
})

Expand Down Expand Up @@ -630,8 +636,6 @@ func Test_Plugin_Observation(t *testing.T) {

assert.Zero(t, p.BenchmarkPrice)
assert.False(t, p.PricesValid)
assert.Zero(t, p.LinkFee)
assert.False(t, p.LinkFeeValid)
})

t.Run("encoding fails on all observations", func(t *testing.T) {
Expand All @@ -648,6 +652,7 @@ func Test_Plugin_Observation(t *testing.T) {
MaxFinalizedTimestamp: mercury.ObsResult[int64]{
Val: rand.Int63(),
},
// encoding never fails on calculated fees
LinkPrice: mercury.ObsResult[*big.Int]{
Val: new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil),
},
Expand All @@ -668,10 +673,6 @@ func Test_Plugin_Observation(t *testing.T) {
assert.Zero(t, p.Bid)
assert.Zero(t, p.Ask)
assert.False(t, p.PricesValid)
assert.Zero(t, p.LinkFee)
assert.False(t, p.LinkFeeValid)
assert.Zero(t, p.NativeFee)
assert.False(t, p.NativeFeeValid)
})
}

Expand Down

0 comments on commit e2f9fcf

Please sign in to comment.