Skip to content

Commit

Permalink
fix: special asset pairs bugfix and tests (#2205)
Browse files Browse the repository at this point in the history
* weighted coin sorting

* ++

* test weightedDecCoins total

* add edge case resilience to WeightedDecCoins.Add

* ++

* ++

* string methods and WeightedDecCoins Add test

* lint

* WeidhgtedDecCoins Sub test and edge case clarification

* fix matching denom WeightedNormalPair sorting bug

* test WeightedNormalPairs before()

* test WeightedSpecialPairs before()

* canCombine tests

* WeightedSpecialPairs add test

* ++

* ++

* more zero-value cases

* Update x/leverage/types/string.go

Co-authored-by: Robert Zaremba <[email protected]>

* fix suggestion

* improve wdc Sub per suggestion

* move string funcs to util/sdkutil

* move string funcs to util/sdkutil

* simplify position stringers

* stringer tests

* fix test

* suggestion++

Co-authored-by: Robert Zaremba <[email protected]>

* suggestion++

Co-authored-by: Robert Zaremba <[email protected]>

* improve efficiency using strings.Join

* provide examples

* lint

* add negative examples to test

* differing weight cases

* reduce repeated verbose test cases

---------

Co-authored-by: Robert Zaremba <[email protected]>
  • Loading branch information
toteki and robert-zaremba authored Sep 6, 2023
1 parent fa4f814 commit b0ab4c4
Show file tree
Hide file tree
Showing 9 changed files with 1,135 additions and 76 deletions.
32 changes: 32 additions & 0 deletions util/sdkutil/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package sdkutil

import (
"fmt"
"strings"

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

// FormatDec formats a sdk.Dec as a string with no trailing zeroes after the decimal point,
// omitting the decimal point as well for whole numbers.
// e.g. 4.000 -> 4 and 3.500 -> 3.5
func FormatDec(d sdk.Dec) string {
dStr := d.String()
parts := strings.Split(dStr, ".")
if len(parts) != 2 {
return dStr
}
integer, decimal := parts[0], parts[1]
decimal = strings.TrimRight(decimal, "0") // no need for trailing zeros after the "."
if decimal == "" {
return integer
}
return fmt.Sprint(integer, ".", decimal)
}

// FormatDecCoin formats a sdk.DecCoin with no trailing zeroes after the decimal point in its amount,
// omitting the decimal point as well for whole numbers. Also places a space between amount and denom.
// e.g. 4.000uumee -> 4 uumee and 3.500ibc/abcd -> 3.5 ibc/abcd
func FormatDecCoin(c sdk.DecCoin) string {
return fmt.Sprintf("%s %s", FormatDec(c.Amount), c.Denom)
}
97 changes: 97 additions & 0 deletions util/sdkutil/string_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package sdkutil

import (
"testing"

"gotest.tools/v3/assert"

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

func TestFormatDec(t *testing.T) {
type testCase struct {
input string
output string
}

testCases := []testCase{
{
"0",
"0",
},
{
"1.00",
"1",
},
{
"1.23",
"1.23",
},
{
"1.500000",
"1.5",
},
{
"1234.567800000",
"1234.5678",
},
{
"-250.02130",
"-250.0213",
},
{
"-0.73190",
"-0.7319",
},
}

for _, tc := range testCases {
assert.Equal(t,
tc.output,
FormatDec(sdk.MustNewDecFromStr(tc.input)),
)
}
}

func TestFormatDecCoin(t *testing.T) {
type testCase struct {
amount string
denom string
output string
}

testCases := []testCase{
{
"1.00",
"AAAA",
"1 AAAA",
},
{
"1.23",
"BBBB",
"1.23 BBBB",
},
{
"1.500000",
"ibc/CCCC",
"1.5 ibc/CCCC",
},
{
"1234.567800000",
"u/DDDD",
"1234.5678 u/DDDD",
},
{
"0",
"EEEE",
"0 EEEE",
},
}

for _, tc := range testCases {
assert.Equal(t,
tc.output,
FormatDecCoin(sdk.NewDecCoinFromDec(tc.denom, sdk.MustNewDecFromStr(tc.amount))),
)
}
}
40 changes: 20 additions & 20 deletions x/leverage/types/documented_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,14 @@ func TestMaxBorrowScenarioA(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t,
"special:\n"+
" 40.000000000000000000AAAA, 20.000000000000000000BBBB, 0.500000000000000000\n"+
" 0.000000000000000000BBBB, 0.000000000000000000AAAA, 0.500000000000000000\n"+
" 50.000000000000000000AAAA, 20.000000000000000000CCCC, 0.400000000000000000\n"+
" 0.000000000000000000CCCC, 0.000000000000000000AAAA, 0.400000000000000000\n"+
" {0.5, 40 AAAA, 20 BBBB}\n"+
" {0.5, 0 BBBB, 0 AAAA}\n"+
" {0.4, 50 AAAA, 20 CCCC}\n"+
" {0.4, 0 CCCC, 0 AAAA}\n"+
"normal:\n"+
" {10.000000000000000000AAAA 0.400000000000000000}, {1.000000000000000000DDDD 0.100000000000000000}\n"+
" {40.000000000000000000DDDD 0.100000000000000000}, {4.000000000000000000DDDD 0.100000000000000000}\n"+
" {260.000000000000000000DDDD 0.100000000000000000}, -\n",
" [10 AAAA (0.4), 1 DDDD (0.1)]\n"+
" [40 DDDD (0.1), 4 DDDD (0.1)]\n"+
" [260 DDDD (0.1), -]",
initialPosition.String(),
)
borrowLimit := initialPosition.Limit()
Expand All @@ -64,13 +64,13 @@ func TestMaxBorrowScenarioA(t *testing.T) {
// pair with B, demoting the C borrow to ordinary assets.
assert.Equal(t,
"special:\n"+
" 50.000000000000000000AAAA, 25.000000000000000000BBBB, 0.500000000000000000\n"+
" 0.000000000000000000BBBB, 0.000000000000000000AAAA, 0.500000000000000000\n"+
" 50.000000000000000000AAAA, 20.000000000000000000CCCC, 0.400000000000000000\n"+
" 0.000000000000000000CCCC, 0.000000000000000000AAAA, 0.400000000000000000\n"+
" {0.5, 50 AAAA, 25 BBBB}\n"+
" {0.5, 0 BBBB, 0 AAAA}\n"+
" {0.4, 50 AAAA, 20 CCCC}\n"+
" {0.4, 0 CCCC, 0 AAAA}\n"+
"normal:\n"+
" {250.000000000000000000DDDD 0.100000000000000000}, {25.000000000000000000BBBB 0.300000000000000000}\n"+
" {50.000000000000000000DDDD 0.100000000000000000}, {5.000000000000000000DDDD 0.100000000000000000}\n",
" [250 DDDD (0.1), 25 BBBB (0.3)]\n"+
" [50 DDDD (0.1), 5 DDDD (0.1)]",
initialPosition.String(),
)

Expand Down Expand Up @@ -104,14 +104,14 @@ func TestMaxBorrowScenarioA(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t,
"special:\n"+
" 100.000000000000000000AAAA, 50.000000000000000000BBBB, 0.500000000000000000\n"+
" 0.000000000000000000BBBB, 0.000000000000000000AAAA, 0.500000000000000000\n"+
" 0.000000000000000000AAAA, 0.000000000000000000CCCC, 0.400000000000000000\n"+
" 0.000000000000000000CCCC, 0.000000000000000000AAAA, 0.400000000000000000\n"+
" {0.5, 100 AAAA, 50 BBBB}\n"+
" {0.5, 0 BBBB, 0 AAAA}\n"+
" {0.4, 0 AAAA, 0 CCCC}\n"+
" {0.4, 0 CCCC, 0 AAAA}\n"+
"normal:\n"+
" {50.000000000000000000DDDD 0.100000000000000000}, {5.000000000000000000BBBB 0.300000000000000000}\n"+
" {200.000000000000000000DDDD 0.100000000000000000}, {20.000000000000000000CCCC 0.200000000000000000}\n"+
" {50.000000000000000000DDDD 0.100000000000000000}, {5.000000000000000000DDDD 0.100000000000000000}\n",
" [50 DDDD (0.1), 5 BBBB (0.3)]\n"+
" [200 DDDD (0.1), 20 CCCC (0.2)]\n"+
" [50 DDDD (0.1), 5 DDDD (0.1)]",
finalPosition.String(),
)
}
18 changes: 0 additions & 18 deletions x/leverage/types/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,6 @@ type AccountPosition struct {
minimumBorrowFactor sdk.Dec
}

func (ap *AccountPosition) String() string {
s := "special:\n"
for _, wsp := range ap.specialPairs {
s += fmt.Sprintf(" %s, %s, %s\n", wsp.Collateral, wsp.Borrow, wsp.SpecialWeight)
}
s += "normal:\n"
for _, wnp := range ap.normalPairs {
s += fmt.Sprintf(" %s, %s\n", wnp.Collateral, wnp.Borrow)
}
for _, sc := range ap.unpairedCollateral {
s += fmt.Sprintf(" %s, -\n", sc)
}
for _, sb := range ap.unpairedBorrows {
s += fmt.Sprintf(" - , %s\n", sb)
}
return s
}

// NewAccountPosition creates and sorts an account position based on token settings,
// special asset pairs, and the collateral and borrowed value of each token in an account.
// Once this structure is created, borrow limit calculations can be performed without
Expand Down
2 changes: 1 addition & 1 deletion x/leverage/types/position_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,4 +772,4 @@ func TestMaxWithdrawNoSpecialPairs(t *testing.T) {

// TODO: more cases for positions with multiple borrow and collateral types
// TODO: max borrow and max withdraw tests with special pairs involved
// TODO: clever zero cases, such as max withdraw something that does not exist
// TODO: clever zero cases, such as max withdraw something that does not exist and missing prices (zero amounts)
59 changes: 59 additions & 0 deletions x/leverage/types/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package types

import (
"fmt"
"strings"

"github.com/umee-network/umee/v6/util/sdkutil"
)

func (ap *AccountPosition) String() string {
special := []string{}
normal := []string{}
for _, wsp := range ap.specialPairs {
special = append(special, wsp.String())
}
for _, wnp := range ap.normalPairs {
normal = append(normal, wnp.String())
}
for _, c := range ap.unpairedCollateral {
normal = append(normal, fmt.Sprintf("[%s, -]", c))
}
for _, b := range ap.unpairedBorrows {
normal = append(normal, fmt.Sprintf("[-, %s]", b))
}
sep := "\n "
return fmt.Sprint(
"special:", sep,
strings.Join(special, sep),
"\nnormal:", sep,
strings.Join(normal, sep),
)
}

// String represents a WeightedNormalPair in the form [WeightedDecCoin, WeightedDecCoin]
// e.g. [10 uumee (0.35), 3.5 uumee (0.35)]
func (wnp WeightedNormalPair) String() string {
return fmt.Sprintf("[%s, %s]", wnp.Collateral, wnp.Borrow)
}

// String represents a WeightedSpecialPair in the form [weight, DecCoin, DecCoin]
// e.g. {0.35, 10 uumee, 3.5 uumee}
func (wsp WeightedSpecialPair) String() string {
return fmt.Sprintf(
"{%s, %s, %s}",
sdkutil.FormatDec(wsp.SpecialWeight),
sdkutil.FormatDecCoin(wsp.Collateral),
sdkutil.FormatDecCoin(wsp.Borrow),
)
}

// String represents a WeightedDecCoin in the form coin (weight)
// e.g. 10 uumee (0.35)
func (wdc WeightedDecCoin) String() string {
return fmt.Sprintf(
"%s (%s)",
sdkutil.FormatDecCoin(wdc.Asset),
sdkutil.FormatDec(wdc.Weight),
)
}
4 changes: 2 additions & 2 deletions x/leverage/types/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func validToken() types.Token {
}
}

func TestUpdateRegistryProposal_String(t *testing.T) {
func TestUpdateRegistryProposalString(t *testing.T) {
token := validToken()
token.ReserveFactor = sdk.NewDec(40)
p := types.MsgGovUpdateRegistry{
Expand Down Expand Up @@ -68,7 +68,7 @@ updatetokens: []
assert.Equal(t, expected, p.String())
}

func TestToken_Validate(t *testing.T) {
func TestTokenValidate(t *testing.T) {
invalidBaseToken := validToken()
invalidBaseToken.BaseDenom = "$$"
invalidBaseToken.SymbolDenom = ""
Expand Down
Loading

0 comments on commit b0ab4c4

Please sign in to comment.