Skip to content

Commit

Permalink
[1658]: Create ParseFeeRatio.
Browse files Browse the repository at this point in the history
  • Loading branch information
SpicyLemon committed Oct 17, 2023
1 parent 13820fb commit 30e1ceb
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
39 changes: 39 additions & 0 deletions x/exchange/market.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,45 @@ func ValidateBuyerFeeRatios(ratios []FeeRatio) error {
return errors.Join(errs...)
}

// parseCoin parses a string into an sdk.Coin
func parseCoin(coinStr string) (sdk.Coin, error) {
// The sdk.ParseCoinNormalized allows for decimals and just truncates if there are some.
// But I want an error if there's a decimal portion.
// It's errors also always have "invalid decimal coin expression", and I don't want "decimal" in these errors.
decCoin, err := sdk.ParseDecCoin(coinStr)
if err != nil || !decCoin.Amount.IsInteger() {
return sdk.Coin{}, fmt.Errorf("invalid coin expression: %q", coinStr)
}
coin, _ := decCoin.TruncateDecimal()
return coin, nil
}

// ParseFeeRatio parses a "<price>:<fee>" string into a FeeRatio.
func ParseFeeRatio(ratio string) (*FeeRatio, error) {
parts := strings.Split(ratio, ":")
if len(parts) != 2 {
return nil, fmt.Errorf("cannot create FeeRatio from %q: expected exactly one colon", ratio)
}
price, err := parseCoin(parts[0])
if err != nil {
return nil, fmt.Errorf("cannot create FeeRatio from %q: price: %w", ratio, err)
}
fee, err := parseCoin(parts[1])
if err != nil {
return nil, fmt.Errorf("cannot create FeeRatio from %q: fee: %w", ratio, err)
}
return &FeeRatio{Price: price, Fee: fee}, nil
}

// MustParseFeeRatio parses a "<price>:<fee>" string into a FeeRatio, panicking if there's a problem.
func MustParseFeeRatio(ratio string) FeeRatio {
rv, err := ParseFeeRatio(ratio)
if err != nil {
panic(err)
}
return *rv
}

// String returns a string representation of this FeeRatio.
func (r FeeRatio) String() string {
return fmt.Sprintf("%s:%s", r.Price, r.Fee)
Expand Down
158 changes: 158 additions & 0 deletions x/exchange/market_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,164 @@ func TestValidateBuyerFeeRatios(t *testing.T) {
}
}

func TestParseFeeRatio(t *testing.T) {
ratioStr := func(ratio *FeeRatio) string {
if ratio == nil {
return "<nil>"
}
return fmt.Sprintf("%q", ratio.String())
}

tests := []struct {
name string
ratio string
expRatio *FeeRatio
expErr string
}{
{
name: "no colons",
ratio: "8banana",
expErr: "expected exactly one colon",
},
{
name: "two colons",
ratio: "8apple:5banana:3cactus",
expErr: "expected exactly one colon",
},
{
name: "one colon: first char",
ratio: ":18banana",
expErr: "price: invalid coin expression: \"\"",
},
{
name: "one colon: las char",
ratio: "33apple:",
expErr: "fee: invalid coin expression: \"\"",
},
{
name: "bad price coin",
ratio: "1234:5banana",
expErr: "price: invalid coin expression: \"1234\"",
},
{
name: "bad fee coin",
ratio: "1234apple:banana",
expErr: "fee: invalid coin expression: \"banana\"",
},
{
name: "neg price coin",
ratio: "-55apple:3banana",
expErr: "price: invalid coin expression: \"-55apple\"",
},
{
name: "neg fee coin",
ratio: "55apple:-3banana",
expRatio: nil,
expErr: "fee: invalid coin expression: \"-3banana\"",
},
{
name: "zero price coin",
ratio: "0apple:21banana",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 0),
Fee: sdk.NewInt64Coin("banana", 21),
},
},
{
name: "zero fee coin",
ratio: "5apple:0banana",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 5),
Fee: sdk.NewInt64Coin("banana", 0),
},
},
{
name: "same denoms: price more",
ratio: "30apple:29apple",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 30),
Fee: sdk.NewInt64Coin("apple", 29),
},
},
{
name: "same denoms: price same",
ratio: "30apple:30apple",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 30),
Fee: sdk.NewInt64Coin("apple", 30),
},
},
{
name: "same denoms: price less",
ratio: "30apple:31apple",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 30),
Fee: sdk.NewInt64Coin("apple", 31),
},
},
{
name: "diff denoms: price more",
ratio: "30apple:29banana",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 30),
Fee: sdk.NewInt64Coin("banana", 29),
},
},
{
name: "diff denoms: price same",
ratio: "30apple:30banana",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 30),
Fee: sdk.NewInt64Coin("banana", 30),
},
},
{
name: "diff denoms: price less",
ratio: "30apple:31banana",
expRatio: &FeeRatio{
Price: sdk.NewInt64Coin("apple", 30),
Fee: sdk.NewInt64Coin("banana", 31),
},
},
{
name: "price has decimal",
ratio: "123.4apple:5banana",
expErr: "price: invalid coin expression: \"123.4apple\"",
},
{
name: "fee has decimal",
ratio: "123apple:5.6banana",
expErr: "fee: invalid coin expression: \"5.6banana\"",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if len(tc.expErr) > 0 {
tc.expErr = fmt.Sprintf("cannot create FeeRatio from %q: %s", tc.ratio, tc.expErr)
}

var ratio *FeeRatio
var err error
testFunc := func() {
ratio, err = ParseFeeRatio(tc.ratio)
}
require.NotPanics(t, testFunc, "ParseFeeRatio(%q)", tc.ratio)
assertions.AssertErrorValue(t, err, tc.expErr, "ParseFeeRatio(%q)", tc.ratio)
assert.Equal(t, ratioStr(tc.expRatio), ratioStr(ratio), "ParseFeeRatio(%q)", tc.ratio)

var ratioMust FeeRatio
testFuncMust := func() {
ratioMust = MustParseFeeRatio(tc.ratio)
}
assertions.RequirePanicEquals(t, testFuncMust, tc.expErr, "MustParseFeeRatio(%q)", tc.ratio)
if tc.expRatio != nil {
assert.Equal(t, ratioStr(tc.expRatio), ratioStr(&ratioMust), "MustParseFeeRatio(%q)", tc.ratio)
}
})
}
}

func TestFeeRatio_String(t *testing.T) {
coin := func(amount int64, denom string) sdk.Coin {
return sdk.Coin{Denom: denom, Amount: sdkmath.NewInt(amount)}
Expand Down

0 comments on commit 30e1ceb

Please sign in to comment.