-
Notifications
You must be signed in to change notification settings - Fork 13
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
Allow unlisted denoms in the /custom-direct-quote #540
base: v26.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
package mvc_test | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/osmosis-labs/sqs/domain/mocks" | ||
"github.com/osmosis-labs/sqs/domain/mvc" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
const ( | ||
denom = "uatom" | ||
ibcDenom = "ibc/" + denom | ||
) | ||
|
||
func TestValidateChainDenomQueryParam(t *testing.T) { | ||
|
||
baseDenom, err := sdk.GetBaseDenom() | ||
require.NoError(t, err) | ||
|
||
defaultError := errors.New("default error") | ||
|
||
// Test cases | ||
tests := []struct { | ||
name string | ||
denom string | ||
isHumanDenoms bool | ||
doesAllowUnlistedDenoms bool | ||
mock *mocks.TokensUsecaseMock | ||
expectedDenom string | ||
expectedError string | ||
baseError bool // true if we expect base denom error | ||
}{ | ||
{ | ||
name: "Error validating base denom", | ||
denom: "not" + baseDenom, | ||
isHumanDenoms: true, | ||
doesAllowUnlistedDenoms: true, | ||
mock: &mocks.TokensUsecaseMock{ | ||
GetChainDenomFunc: func(humanDenom string) (string, error) { | ||
return "", defaultError | ||
}, | ||
}, | ||
expectedDenom: "", | ||
expectedError: defaultError.Error(), | ||
}, | ||
{ | ||
name: "Human denom conversion success", | ||
denom: denom, | ||
isHumanDenoms: true, | ||
doesAllowUnlistedDenoms: true, | ||
mock: &mocks.TokensUsecaseMock{ | ||
GetChainDenomFunc: func(humanDenom string) (string, error) { | ||
return "ibc/" + humanDenom, nil | ||
}, | ||
}, | ||
expectedDenom: ibcDenom, | ||
expectedError: "", | ||
}, | ||
{ | ||
name: "Human denom conversion success", | ||
denom: denom, | ||
isHumanDenoms: true, | ||
doesAllowUnlistedDenoms: true, | ||
mock: &mocks.TokensUsecaseMock{ | ||
GetChainDenomFunc: func(humanDenom string) (string, error) { | ||
return ibcDenom, nil | ||
}, | ||
}, | ||
expectedDenom: ibcDenom, | ||
expectedError: "", | ||
}, | ||
{ | ||
name: "Human denom conversion error", | ||
denom: "invalid", | ||
isHumanDenoms: true, | ||
doesAllowUnlistedDenoms: true, | ||
mock: &mocks.TokensUsecaseMock{ | ||
GetChainDenomFunc: func(humanDenom string) (string, error) { | ||
return "", fmt.Errorf("invalid denom") | ||
}, | ||
}, | ||
expectedDenom: "", | ||
expectedError: "invalid denom", | ||
}, | ||
{ | ||
name: "Valid unlisted chain denom", | ||
denom: ibcDenom, | ||
isHumanDenoms: false, | ||
doesAllowUnlistedDenoms: true, | ||
mock: &mocks.TokensUsecaseMock{ | ||
IsValidChainDenomFunc: func(chainDenom string) bool { | ||
return true | ||
}, | ||
}, | ||
expectedDenom: ibcDenom, | ||
expectedError: "", | ||
}, | ||
{ | ||
name: "Invalid chain denom with unlisted allowed", | ||
denom: "invalid", | ||
isHumanDenoms: false, | ||
doesAllowUnlistedDenoms: true, | ||
mock: &mocks.TokensUsecaseMock{ | ||
IsValidChainDenomFunc: func(chainDenom string) bool { | ||
return false | ||
}, | ||
}, | ||
expectedDenom: "", | ||
expectedError: "denom is not a valid chain denom (invalid)", | ||
}, | ||
{ | ||
name: "Unlisted chain denom when not allowed", | ||
denom: ibcDenom, | ||
isHumanDenoms: false, | ||
doesAllowUnlistedDenoms: false, | ||
mock: &mocks.TokensUsecaseMock{ | ||
IsValidListedDenomFunc: func(denom string) bool { | ||
return false | ||
}, | ||
}, | ||
expectedDenom: "", | ||
expectedError: "denom is not a valid listed chain denom (" + ibcDenom + ")", | ||
}, | ||
{ | ||
name: "Valid listed chain denom", | ||
denom: ibcDenom, | ||
isHumanDenoms: false, | ||
doesAllowUnlistedDenoms: false, | ||
mock: &mocks.TokensUsecaseMock{ | ||
IsValidListedDenomFunc: func(denom string) bool { | ||
return true | ||
}, | ||
}, | ||
expectedDenom: ibcDenom, | ||
expectedError: "", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// System under test | ||
result, err := mvc.ValidateChainDenomQueryParam(tt.mock, tt.denom, tt.isHumanDenoms, tt.doesAllowUnlistedDenoms) | ||
|
||
// Assert results | ||
if tt.baseError { | ||
assert.Empty(t, result) | ||
assert.NoError(t, err) | ||
} else if tt.expectedError != "" { | ||
assert.Empty(t, result) | ||
assert.EqualError(t, err, tt.expectedError) | ||
} else { | ||
assert.Equal(t, tt.expectedDenom, result) | ||
assert.NoError(t, err) | ||
} | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -105,7 +105,9 @@ func (a *RouterHandler) GetOptimalQuote(c echo.Context) (err error) { | |
tokenIn, tokenOutDenom = req.TokenOut, req.TokenInDenom | ||
} | ||
|
||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, []string{tokenIn.Denom, tokenOutDenom}) | ||
// We do not allow unlisted denoms since this is a production from the swap tool. | ||
doesAllowUnlistedDenoms := false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we allow client to specify There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm not sure - we don't have a product use case for this. |
||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, []string{tokenIn.Denom, tokenOutDenom}, doesAllowUnlistedDenoms) | ||
if err != nil { | ||
return c.JSON(http.StatusBadRequest, domain.ResponseError{Message: err.Error()}) | ||
} | ||
|
@@ -200,7 +202,10 @@ func (a *RouterHandler) GetDirectCustomQuote(c echo.Context) (err error) { | |
} | ||
|
||
// Apply human denoms conversion if required. | ||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, append([]string{tokenIn.Denom}, tokenOutDenom...)) | ||
// We allow unlisted denoms since this is a production endpoint from the pools page | ||
// where we want to support all chain denoms. | ||
doesAllowUnlistedDenoms := true | ||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, append([]string{tokenIn.Denom}, tokenOutDenom...), doesAllowUnlistedDenoms) | ||
if err != nil { | ||
return c.JSON(http.StatusBadRequest, domain.ResponseError{Message: err.Error()}) | ||
} | ||
|
@@ -250,7 +255,10 @@ func (a *RouterHandler) GetCandidateRoutes(c echo.Context) error { | |
return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) | ||
} | ||
|
||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, []string{tokenIn, tokenOutDenom}) | ||
// We do not allow unlisted denoms to maintain | ||
// compatibility with the /quote endpoint. | ||
doesAllowUnlistedDenoms := false | ||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, []string{tokenIn, tokenOutDenom}, doesAllowUnlistedDenoms) | ||
if err != nil { | ||
return c.JSON(domain.GetStatusCode(err), domain.ResponseError{Message: err.Error()}) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,7 +141,9 @@ func (a *TokensHandler) GetPoolDenomMetadata(c echo.Context) (err error) { | |
|
||
denoms := strings.Split(denomsStr, ",") | ||
// Validate denom parameters and convert to chain denoms if necessary. | ||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, denoms) | ||
// We allow unlisted denoms since this is a debug endpoint. | ||
doesAllowUnlistedDenoms := true | ||
chainDenoms, err := mvc.ValidateChainDenomsQueryParam(c, a.TUsecase, denoms, doesAllowUnlistedDenoms) | ||
Comment on lines
+144
to
+146
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Based on the gathered information, I can now provide the final response: Review of The codebase shows consistent and well-documented usage of
However, there's a lack of direct test coverage for 🔗 Analysis chainVerify test coverage and usage consistency. Let's verify the test coverage and usage consistency for this change. Let me generate additional verification scripts to check for test coverage since the previous fd command had an error. Let me check the test coverage in the mvc package since we found a test file there. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check test coverage and other usages of ValidateChainDenomsQueryParam
# Test 1: Check for test files covering this endpoint
echo "Checking for test files..."
fd -e go -e _test.go tokens/delivery/http
# Test 2: Find other usages of ValidateChainDenomsQueryParam
echo "Checking other usages of ValidateChainDenomsQueryParam..."
rg "ValidateChainDenomsQueryParam" -A 3 -B 3
Length of output: 3786 Script: #!/bin/bash
# Check for test files and test coverage
echo "Checking for test files in tokens/delivery/http..."
fd -e go . --full-path "tokens/delivery/http" | grep "_test.go"
# Check implementation of ValidateChainDenomsQueryParam
echo "Checking implementation of ValidateChainDenomsQueryParam..."
ast-grep --pattern 'func ValidateChainDenomsQueryParam($$$) {
$$$
}'
# Check for test files covering mvc package
echo "Checking for test files in domain/mvc..."
fd -e go . --full-path "domain/mvc" | grep "_test.go"
Length of output: 543 Script: #!/bin/bash
# Check test coverage for ValidateChainDenomsQueryParam
echo "Checking test coverage in tokens_test.go..."
rg "TestValidateChainDenomsQueryParam|ValidateChainDenomsQueryParam.*test" -A 10 domain/mvc/tokens_test.go
# Check the actual implementation
echo "Checking implementation details..."
rg "func ValidateChainDenomsQueryParam" -A 15 domain/mvc/tokens.go
Length of output: 888 |
||
if err != nil { | ||
return c.JSON(http.StatusBadRequest, domain.ResponseError{Message: err.Error()}) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -406,18 +406,8 @@ func (t *tokensUseCase) RegisterPricingStrategy(source domain.PricingSourceType, | |
|
||
// IsValidChainDenom implements mvc.TokensUsecase. | ||
func (t *tokensUseCase) IsValidChainDenom(chainDenom string) bool { | ||
metaData, ok := t.tokenMetadataByChainDenom.Load(chainDenom) | ||
if !ok { | ||
return false | ||
} | ||
|
||
v, ok := metaData.(domain.Token) | ||
if !ok { | ||
return false | ||
} | ||
|
||
// is valid only if token is found and is not unlisted | ||
return !v.IsUnlisted | ||
_, ok := t.tokenMetadataByChainDenom.Load(chainDenom) | ||
return ok | ||
} | ||
|
||
// GetMinPoolLiquidityCap implements mvc.TokensUsecase. | ||
|
@@ -466,3 +456,18 @@ func (t *tokensUseCase) GetCoingeckoIdByChainDenom(chainDenom string) (string, e | |
|
||
return v, nil | ||
} | ||
|
||
// IsValidListedDenom implements mvc.TokensUsecase. | ||
func (t *tokensUseCase) IsValidListedDenom(denom string) bool { | ||
metaData, ok := t.tokenMetadataByChainDenom.Load(denom) | ||
if !ok { | ||
return false | ||
} | ||
|
||
v, ok := metaData.(domain.Token) | ||
if !ok { | ||
return false | ||
} | ||
|
||
return !v.IsUnlisted | ||
} | ||
Comment on lines
+460
to
+473
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider optimizing the implementation The implementation is functionally correct but could be more concise. Consider this refactoring: func (t *tokensUseCase) IsValidListedDenom(denom string) bool {
- metaData, ok := t.tokenMetadataByChainDenom.Load(denom)
- if !ok {
- return false
- }
-
- v, ok := metaData.(domain.Token)
- if !ok {
- return false
- }
-
- return !v.IsUnlisted
+ if metaData, ok := t.tokenMetadataByChainDenom.Load(denom); ok {
+ if v, ok := metaData.(domain.Token); ok {
+ return !v.IsUnlisted
+ }
+ }
+ return false
} Additionally, consider adding debug logging for failure cases to aid in troubleshooting: func (t *tokensUseCase) IsValidListedDenom(denom string) bool {
if metaData, ok := t.tokenMetadataByChainDenom.Load(denom); ok {
if v, ok := metaData.(domain.Token); ok {
return !v.IsUnlisted
}
+ t.logger.Debug("Invalid type assertion for token metadata", zap.String("denom", denom))
}
+ t.logger.Debug("Denom not found in metadata", zap.String("denom", denom))
return false
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential panic in mock implementation.
The implementation returns
false
as default behavior, which is good. However, there's an inconsistency with other mock methods in this file that usepanic("unimplemented")
as their default behavior (seeSetTokenRegistryLoader
andClearPoolDenomMetadata
).Consider either: