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

feat!: convert LiquidationIncentive from Dec to uint32 #1149

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
4 changes: 2 additions & 2 deletions app/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ func IntegrationTestNetworkConfig() network.Config {
LiquidationThreshold: sdk.MustNewDecFromStr("0.050000000000000000"),
BaseBorrowRate: sdk.MustNewDecFromStr("0.020000000000000000"),
KinkBorrowRate: sdk.MustNewDecFromStr("0.200000000000000000"),
MaxBorrowRate: sdk.MustNewDecFromStr("1.50000000000000000"),
MaxBorrowRate: 15000, // 1.5
KinkUtilization: sdk.MustNewDecFromStr("0.200000000000000000"),
LiquidationIncentive: sdk.MustNewDecFromStr("0.180000000000000000"),
LiquidationIncentive: 1800,
EnableMsgSupply: true,
EnableMsgBorrow: true,
Blacklist: false,
Expand Down
49 changes: 49 additions & 0 deletions math/bpmath/bp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package bpmath

import (
"math"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// BP assures represents values in basis points. Maximum value is 2^32-1.
// Note: BP operations should not be chained - this causes precision loses.
type BP uint32

// Quo returns a/b in basis points.
// Contract: a>=0 and b > 0.
// Panics if a/b >= MaxUint32/10'000 or if b==0.
func Quo(a, b sdk.Int, rounding Rounding) BP {
return BP(quo(a, b, rounding, math.MaxUint32))
}

func quo(a, b sdk.Int, rounding Rounding, max uint64) uint64 {
if b.IsZero() {
panic("divider can't be zero")
}
bp := a.MulRaw(ONE)
if rounding == UP {
bp = bp.Add(b.SubRaw(1))
}
x := bp.Quo(b).Uint64()
if x > max {
panic("basis points out of band")
}
return x
}

// Mul returns a * b_in_basis_points
// Contract: b \in [0; MatxUint32]
func Mul[T BP | FixedBP](a sdk.Int, b T) sdk.Int {
if b == 0 {
return sdk.ZeroInt()
}
if b == ONE {
return a
}
return a.MulRaw(int64(b)).Quo(oneBigInt)
}

func (bp BP) ToDec() sdk.Dec {
return sdk.NewDecWithPrec(int64(bp), 4)
}
26 changes: 26 additions & 0 deletions math/bpmath/bp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package bpmath

import (
"fmt"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func TestBPToDec(t *testing.T) {
t.Parallel()
tcs := []struct {
name string
a FixedBP
exp sdk.Dec
}{
{"t1", 99999, sdk.MustNewDecFromStr("9.9999")},
{"t2", ONE * 10, sdk.MustNewDecFromStr("10.0")},
}
require := require.New(t)
for _, tc := range tcs {
bp := BP(tc.a).ToDec()
require.Equal(tc.exp.String(), bp.String(), fmt.Sprint("test-bp ", tc.name))
}
}
2 changes: 2 additions & 0 deletions math/bpmath/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package bpmath provides types and functions for doing basis point operations.
package bpmath
20 changes: 20 additions & 0 deletions math/bpmath/fixed_bp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package bpmath

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// FixedBP assures that all oprations are in 0-10'000 range
// Note: FixedBP operations should not be chained - this causes precision loses.
type FixedBP uint32

// FixedQuo returns a/b in basis points. Returns 10'000 if a >= b;
// Contract: a>=0 and b > 0.
// Panics if b==0.
func FixedQuo(a, b sdk.Int, rounding Rounding) FixedBP {
return FixedBP(quo(a, b, rounding, ONE))
}

func (bp FixedBP) ToDec() sdk.Dec {
return sdk.NewDecWithPrec(int64(bp), 4)
}
109 changes: 109 additions & 0 deletions math/bpmath/fixed_bp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package bpmath

import (
"fmt"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func TestFixedQuo(t *testing.T) {
t.Parallel()
tcs := []struct {
name string
a uint64
b uint64
r Rounding
exp FixedBP
panics bool
}{
{"t1", 0, 0, UP, ONE, true},
{"t2", 0, 0, DOWN, ONE, true},
{"t3", 1, 0, UP, ONE, true},
{"t4", 1, 0, DOWN, ONE, true},

{"t5", 20, 10, UP, 0, true},
{"t6", 20, 10, DOWN, 0, true},
{"t7", 20, 20, UP, ONE, false},
{"t7-1", 20, 20, DOWN, ONE, false},

{"t8", 1, 2, UP, ONE / 2, false},
{"t9", 1, 2, DOWN, ONE / 2, false},
{"t10", 1, 3, UP, 3334, false},
{"t11", 1, 3, DOWN, 3333, false},
{"t12", 2, 3, UP, 6667, false},
{"t13", 2, 3, DOWN, 6666, false},
{"t14", 10, 99999, UP, 2, false},
{"t15", 10, 99999, DOWN, 1, false},
{"t16", 10, 99999999, UP, 1, false},
{"t17", 10, 99999999, DOWN, 0, false},
}
require := require.New(t)
for _, tc := range tcs {
a, b := sdk.NewIntFromUint64(tc.a), sdk.NewIntFromUint64(tc.b)
if tc.panics {
require.Panics(func() {
FixedQuo(a, b, tc.r)
}, tc.name)
continue
}
o := FixedQuo(a, b, tc.r)
require.Equal(int(tc.exp), int(o), fmt.Sprint("test ", tc.name))
}
}

func TestFixedMul(t *testing.T) {
t.Parallel()
tcs := []struct {
name string
a uint64
b BP
exp uint64
}{
{"t1", 20, 0, 0},
{"t2", 20, 1, 0},
{"t3", 20, ONE, 20},
{"t4", 20000, 0, 0},
{"t5", 20000, 1, 2},
{"t6", 20000, 2, 4},
{"t7", 20000, half, 10000},
{"t8", 2000, 4, 0},
{"t9", 2000, 5, 1},
{"t10", 2000, half, 1000},
}
require := require.New(t)
for _, tc := range tcs {
a := sdk.NewIntFromUint64(tc.a)
o := Mul(a, tc.b)
require.Equal(int64(tc.exp), o.Int64(), fmt.Sprint("test ", tc.name))

// must work with both FixedBP and BP
o = Mul(a, FixedBP(tc.b))
require.Equal(int64(tc.exp), o.Int64(), fmt.Sprint("test ", tc.name))

}
}

func TestFixedToDec(t *testing.T) {
t.Parallel()
tcs := []struct {
name string
a FixedBP
exp sdk.Dec
}{
{"t1", 0, sdk.ZeroDec()},
{"t2", 1, sdk.MustNewDecFromStr("0.0001")},
{"t3", 20, sdk.MustNewDecFromStr("0.002")},
{"t4", 9999, sdk.MustNewDecFromStr("0.9999")},
{"t5", ONE, sdk.NewDec(1)},
}
require := require.New(t)
for _, tc := range tcs {
o := tc.a.ToDec()
require.Equal(tc.exp.String(), o.String(), fmt.Sprint("test-fixedbp ", tc.name))

bp := BP(tc.a).ToDec()
require.Equal(tc.exp.String(), bp.String(), fmt.Sprint("test-bp ", tc.name))
}
}
23 changes: 23 additions & 0 deletions math/bpmath/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bpmath

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

type Rounding uint

const (
DOWN = iota
UP
)

const ONE = 10000
const half = ONE / 2

var (
oneBigInt sdk.Int
)

func init() {
oneBigInt = sdk.NewIntFromUint64(ONE)
}
16 changes: 8 additions & 8 deletions proto/umee/leverage/v1/leverage.proto
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ message Token {

// The max_borrow_rate defines the interest rate for borrowing this
// asset (seen when supply utilization is 100%).
string max_borrow_rate = 7 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"max_borrow_rate\""
// Value is in basis points; allowed values are in [0, 100'000'000] range.
uint32 max_borrow_rate = 7 [
(gogoproto.casttype) = "github.com/umee-network/umee/v2/math/bpmath.BP",
(gogoproto.moretags) = "yaml:\"max_borrow_rate\""
];

// The kink_utilization defines the value where the kink rate kicks off for
Expand All @@ -111,10 +111,10 @@ message Token {

// The liquidation_incentive determines the portion of bonus collateral of
// a token type liquidators receive as a liquidation reward.
string liquidation_incentive = 9 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"liquidation_incentive\""
// Value is in basis points; allowed values are in [0, 10000] range.
uint32 liquidation_incentive = 9 [
toteki marked this conversation as resolved.
Show resolved Hide resolved
(gogoproto.moretags) = "yaml:\"liquidation_incentive\"",
(gogoproto.casttype) = "github.com/umee-network/umee/v2/math/bpmath.FixedBP"
];

// The symbol_denom and exponent are solely used to update the oracle's accept
Expand Down
4 changes: 2 additions & 2 deletions x/leverage/client/cli/proposal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ func TestParseUpdateRegistryProposal(t *testing.T) {
"liquidation_threshold": "0.05",
"base_borrow_rate": "0.02",
"kink_borrow_rate": "0.2",
"max_borrow_rate": "1.5",
"max_borrow_rate": 15000,
"kink_utilization": "0.2",
"liquidation_incentive": "0.1",
"liquidation_incentive": 1000,
"symbol_denom": "UMEE",
"exponent": 6,
"enable_msg_supply": true,
Expand Down
4 changes: 2 additions & 2 deletions x/leverage/client/tests/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,9 @@ func (s *IntegrationTestSuite) TestLeverageScenario() {
LiquidationThreshold: sdk.MustNewDecFromStr("0.05"),
BaseBorrowRate: sdk.MustNewDecFromStr("0.02"),
KinkBorrowRate: sdk.MustNewDecFromStr("0.2"),
MaxBorrowRate: sdk.MustNewDecFromStr("1.5"),
MaxBorrowRate: 15000,
KinkUtilization: sdk.MustNewDecFromStr("0.2"),
LiquidationIncentive: sdk.MustNewDecFromStr("0.18"),
LiquidationIncentive: 180,
EnableMsgSupply: true,
EnableMsgBorrow: true,
Blacklist: false,
Expand Down
4 changes: 2 additions & 2 deletions x/leverage/gov_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func newTestToken(base, symbol, reserveFactor string) types.Token {
LiquidationThreshold: sdk.MustNewDecFromStr("0.25"),
BaseBorrowRate: sdk.MustNewDecFromStr("0.02"),
KinkBorrowRate: sdk.MustNewDecFromStr("0.22"),
MaxBorrowRate: sdk.MustNewDecFromStr("1.52"),
MaxBorrowRate: 15200,
KinkUtilization: sdk.MustNewDecFromStr("0.8"),
LiquidationIncentive: sdk.MustNewDecFromStr("0.1"),
LiquidationIncentive: 100,
EnableMsgSupply: true,
EnableMsgBorrow: true,
Blacklist: false,
Expand Down
10 changes: 5 additions & 5 deletions x/leverage/keeper/interest.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ func (k Keeper) DeriveBorrowAPY(ctx sdk.Context, denom string) sdk.Dec {

if utilization.GTE(token.KinkUtilization) {
return Interpolate(
utilization, // x
token.KinkUtilization, // x1
token.KinkBorrowRate, // y1
sdk.OneDec(), // x2
token.MaxBorrowRate, // y2
utilization, // x
token.KinkUtilization, // x1
token.KinkBorrowRate, // y1
sdk.OneDec(), // x2
token.MaxBorrowRate.ToDec(), // y2
)
}

Expand Down
11 changes: 6 additions & 5 deletions x/leverage/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/golang-lru/simplelru"
"github.com/tendermint/tendermint/libs/log"

"github.com/umee-network/umee/v2/math/bpmath"
"github.com/umee-network/umee/v2/x/leverage/types"
)

Expand Down Expand Up @@ -432,7 +433,7 @@ func (k Keeper) LiquidateBorrow(
}

// apply liquidation incentive
reward.Amount = reward.Amount.ToDec().Mul(sdk.OneDec().Add(liquidationIncentive)).TruncateInt()
reward.Amount = bpmath.Mul(reward.Amount, liquidationIncentive).Add(reward.Amount)
Copy link
Member

@toteki toteki Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only use of Mul (or Quo) I could find in the current diff

Interestingly enough, the original line had to be removed in my liquidation PR #1118 , because even the small sdk.Dec early rounding was contributing to dust issues. Now liquidation incentive is multiplied to a Dec without rounding back to Int.

I think it is possible to avoid 4-precision Mul and Quo altogether


maxReward := collateral.AmountOf(reward.Denom)
if maxReward.IsZero() {
Expand Down Expand Up @@ -519,17 +520,17 @@ func (k Keeper) LiquidationParams(
rewardDenom string,
borrowed sdk.Dec,
limit sdk.Dec,
) (sdk.Dec, sdk.Dec, error) {
) (bpmath.FixedBP, sdk.Dec, error) {
if borrowed.IsNegative() {
return sdk.ZeroDec(), sdk.ZeroDec(), sdkerrors.Wrap(types.ErrBadValue, borrowed.String())
return 0, sdk.ZeroDec(), sdkerrors.Wrap(types.ErrBadValue, borrowed.String())
}
if limit.IsNegative() {
return sdk.ZeroDec(), sdk.ZeroDec(), sdkerrors.Wrap(types.ErrBadValue, limit.String())
return 0, sdk.ZeroDec(), sdkerrors.Wrap(types.ErrBadValue, limit.String())
}

ts, err := k.GetTokenSettings(ctx, rewardDenom)
if err != nil {
return sdk.ZeroDec(), sdk.ZeroDec(), err
return 0, sdk.ZeroDec(), err
}

// special case: If liquidation threshold is zero, close factor is always 1
Expand Down
Loading