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

[stableswap]: Add checks for invalid cfmm inputs #2695

Merged
merged 2 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 20 additions & 4 deletions x/gamm/pool-models/stableswap/amm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var (

// solidly CFMM is xy(x^2 + y^2) = k
func cfmmConstant(xReserve, yReserve sdk.Dec) sdk.Dec {
if !xReserve.IsPositive() || !yReserve.IsPositive() {
panic("invalid input: reserves must be positive")
}
xy := xReserve.Mul(yReserve)
x2 := xReserve.Mul(xReserve)
y2 := yReserve.Mul(yReserve)
Expand All @@ -28,6 +31,10 @@ func cfmmConstant(xReserve, yReserve sdk.Dec) sdk.Dec {
// of their squares (e.g. v = w^2 + z^2).
// When u = 1 and v = 0, this is equivalent to solidly's CFMM
func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares sdk.Dec) sdk.Dec {
if !xReserve.IsPositive() || !yReserve.IsPositive() || !uReserve.IsPositive() || vSumSquares.IsNegative() {
panic("invalid input: reserves must be positive")
}

xyu := xReserve.Mul(yReserve.Mul(uReserve))
x2 := xReserve.Mul(xReserve)
y2 := yReserve.Mul(yReserve)
Expand All @@ -40,8 +47,8 @@ func cfmmConstantMulti(xReserve, yReserve, uReserve, vSumSquares sdk.Dec) sdk.De
// So we solve the following expression for `a`
// xy(x^2 + y^2) = (x - a)(y + b)((x - a)^2 + (y + b)^2)
func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
if !yReserve.Add(yIn).IsPositive() {
panic("invalid yReserve, yIn combo")
if !xReserve.IsPositive() || !yReserve.IsPositive() || !yIn.IsPositive() {
panic("invalid input: reserves and input must be positive")
}

// use the following wolfram alpha link to solve the equation
Expand Down Expand Up @@ -151,6 +158,11 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
term3 := term3Numerator.Quo(bpy)

a := term1.Sub(term2).Add(term3)

if a.GTE(xReserve) {
panic("invalid output: greater than full pool reserves")
}

return a
}

Expand All @@ -160,8 +172,8 @@ func solveCfmm(xReserve, yReserve, yIn sdk.Dec) sdk.Dec {
// So we solve the following expression for `a`
// xyz(x^2 + y^2 + w) = (x - a)(y + b)z((x - a)^2 + (y + b)^2 + w)
func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn sdk.Dec) sdk.Dec {
if !yReserve.Add(yIn).IsPositive() {
panic("invalid yReserve, yIn combo")
if !xReserve.IsPositive() || !yReserve.IsPositive() || !yIn.IsPositive() {
panic("invalid input: reserves and input must be positive")
}

// Use the following wolfram alpha link to solve the equation
Expand Down Expand Up @@ -256,6 +268,10 @@ func solveCfmmMulti(xReserve, yReserve, wSumSquares, yIn sdk.Dec) sdk.Dec {

a := term1.Sub(term2).Add(term3)

if a.GTE(xReserve) {
panic("invalid output: greater than full pool reserves")
}

return a
}

Expand Down
151 changes: 119 additions & 32 deletions x/gamm/pool-models/stableswap/amm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,85 +18,172 @@ type StableSwapTestSuite struct {
func TestCFMMInvariantTwoAssets(t *testing.T) {
kErrTolerance := sdk.OneDec()

tests := []struct {
xReserve sdk.Dec
yReserve sdk.Dec
yIn sdk.Dec
tests := map[string]struct {
xReserve sdk.Dec
yReserve sdk.Dec
yIn sdk.Dec
expectPanic bool
}{
{
"small pool small input": {
sdk.NewDec(100),
sdk.NewDec(100),
sdk.NewDec(1),
false,
},
{
"small pool large input": {
sdk.NewDec(100),
sdk.NewDec(100),
sdk.NewDec(1000),
false,
},
// This test fails due to a bug in our original solver
// {
// sdk.NewDec(100000),
// sdk.NewDec(100000),
// sdk.NewDec(10000),
// },

// panic catching
"xReserve negative": {
sdk.NewDec(-100),
sdk.NewDec(100),
sdk.NewDec(1),
true,
},
"yReserve negative": {
sdk.NewDec(100),
sdk.NewDec(-100),
sdk.NewDec(1),
true,
},
"yIn negative": {
sdk.NewDec(100),
sdk.NewDec(100),
sdk.NewDec(-1),
true,
},
}

for _, test := range tests {
// using two-asset cfmm
k0 := cfmmConstant(test.xReserve, test.yReserve)
xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn)

k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn))
osmoassert.DecApproxEq(t, k0, k1, kErrTolerance)

// using multi-asset cfmm (should be equivalent with u = 1, w = 0)
k2 := cfmmConstantMulti(test.xReserve, test.yReserve, sdk.OneDec(), sdk.ZeroDec())
osmoassert.DecApproxEq(t, k2, k0, kErrTolerance)
xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, sdk.ZeroDec(), test.yIn)
fmt.Println(xOut2)
k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), sdk.OneDec(), sdk.ZeroDec())
osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
for name, test := range tests {
t.Run(name, func(t *testing.T) {
// system under test
sut := func() {
// using two-asset cfmm
k0 := cfmmConstant(test.xReserve, test.yReserve)
xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn)

k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn))
osmoassert.DecApproxEq(t, k0, k1, kErrTolerance)

// using multi-asset cfmm (should be equivalent with u = 1, w = 0)
k2 := cfmmConstantMulti(test.xReserve, test.yReserve, sdk.OneDec(), sdk.ZeroDec())
osmoassert.DecApproxEq(t, k2, k0, kErrTolerance)
xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, sdk.ZeroDec(), test.yIn)
k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), sdk.OneDec(), sdk.ZeroDec())
osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
}

osmoassert.ConditionalPanic(t, test.expectPanic, sut)
})
}
}

func TestCFMMInvariantMultiAssets(t *testing.T) {
kErrTolerance := sdk.OneDec()

tests := []struct {
tests := map[string]struct {
xReserve sdk.Dec
yReserve sdk.Dec
uReserve sdk.Dec
wSumSquares sdk.Dec
yIn sdk.Dec
expectPanic bool
}{
{
"4-asset pool, small input": {
sdk.NewDec(100),
sdk.NewDec(100),
// represents a 4-asset pool with 100 in each reserve
sdk.NewDec(200),
sdk.NewDec(20000),
sdk.NewDec(1),
false,
},
{
"4-asset pool, large input": {
sdk.NewDec(100),
sdk.NewDec(100),
sdk.NewDec(200),
sdk.NewDec(20000),
sdk.NewDec(1000),
false,
},
// {
// This test fails due to a bug in our original solver
// "large pool, large input": {
// sdk.NewDec(100000),
// sdk.NewDec(100000),
// sdk.NewDec(10000),
// },

// panic catching
"negative xReserve": {
sdk.NewDec(-100),
sdk.NewDec(100),
// represents a 4-asset pool with 100 in each reserve
sdk.NewDec(200),
sdk.NewDec(20000),
sdk.NewDec(1),
true,
},
"negative yReserve": {
sdk.NewDec(100),
sdk.NewDec(-100),
// represents a 4-asset pool with 100 in each reserve
sdk.NewDec(200),
sdk.NewDec(20000),
sdk.NewDec(1),
true,
},
"negative uReserve": {
sdk.NewDec(100),
sdk.NewDec(100),
// represents a 4-asset pool with 100 in each reserve
sdk.NewDec(-200),
sdk.NewDec(20000),
sdk.NewDec(1),
true,
},
"negative sumSquares": {
sdk.NewDec(100),
sdk.NewDec(100),
// represents a 4-asset pool with 100 in each reserve
sdk.NewDec(200),
sdk.NewDec(-20000),
sdk.NewDec(1),
true,
},
"negative yIn": {
sdk.NewDec(100),
sdk.NewDec(100),
// represents a 4-asset pool with 100 in each reserve
sdk.NewDec(200),
sdk.NewDec(-20000),
sdk.NewDec(1),
true,
},
}

for _, test := range tests {
// using multi-asset cfmm
k2 := cfmmConstantMulti(test.xReserve, test.yReserve, test.uReserve, test.wSumSquares)
xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, test.wSumSquares, test.yIn)
fmt.Println(xOut2)
k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), test.uReserve, test.wSumSquares)
osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
for name, test := range tests {
t.Run(name, func(t *testing.T) {
// system under test
sut := func() {
// using multi-asset cfmm
k2 := cfmmConstantMulti(test.xReserve, test.yReserve, test.uReserve, test.wSumSquares)
xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, test.wSumSquares, test.yIn)
k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), test.uReserve, test.wSumSquares)
osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
}

osmoassert.ConditionalPanic(t, test.expectPanic, sut)
})
}
}

Expand Down