Skip to content

Commit

Permalink
add missing checks for transmuter's limiter (#554) (#556)
Browse files Browse the repository at this point in the history
(cherry picked from commit 70cdc3b)

Co-authored-by: Supanat <[email protected]>
  • Loading branch information
mergify[bot] and iboss-ptk authored Nov 12, 2024
1 parent ecc7019 commit 8362323
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 46 deletions.
109 changes: 69 additions & 40 deletions router/usecase/pools/routable_cw_alloy_transmuter_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package pools
import (
"context"
"fmt"
"strings"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -29,10 +28,6 @@ type routableAlloyTransmuterPoolImpl struct {
SpreadFactor osmomath.Dec "json:\"spread_factor\""
}

const (
alloyedLPShareDenomComponent = "all"
)

// GetId implements domain.RoutablePool.
func (r *routableAlloyTransmuterPoolImpl) GetId() uint64 {
return r.ChainPool.PoolId
Expand Down Expand Up @@ -187,7 +182,6 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke
}

// Check static upper rate limiter
// We only need to check it for the token in coin since that is the only one that is increased by the current quote.
if err := r.checkStaticRateLimiter(tokenIn); err != nil {
return osmomath.BigDec{}, err
}
Expand All @@ -202,9 +196,14 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke
return tokenOutAmount, nil
}

// checkStaticRateLimiter checks the static rate limiter for the token in coin.
// checkStaticRateLimiter checks the static rate limiter.
// If token in denom is not alloyed, we only need to validate the token in balance.
// Since the token in balance is the only one that is increased by the current quote.
//
// If token in denom is alloyed, we need to validate all assets' balances except token out.
// Since the token out composition is decreasing, other assets' weights are increasing.
//
// Note: static rate limit only has an upper limit.
// Therefore, we only need to validate the token in balance.
// No-op if the static rate limiter is not set.
// Returns error if the token in weight is greater than the upper limit.
// Returns nil if the token in weight is less than or equal to the upper limit.
Expand All @@ -214,12 +213,6 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk
return nil
}

// Check if the static rate limiter exists for the token in denom updated balance.
tokeInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInCoin.Denom)
if !ok {
return nil
}

preComputedData := r.AlloyTransmuterData.PreComputedData
normalizationFactors := preComputedData.NormalizationScalingFactors

Expand All @@ -232,8 +225,8 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk
assetConfig := r.AlloyTransmuterData.AssetConfigs[i]
assetDenom := assetConfig.Denom

// Skip if the asset is alloyed LP hsare
if strings.Contains(assetDenom, alloyedLPShareDenomComponent) {
// Skip if the asset is alloyed LP share
if assetDenom == r.AlloyTransmuterData.AlloyedDenom {
continue
}

Expand All @@ -244,6 +237,11 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk
assetBalance = assetBalance.Add(tokenInCoin.Amount)
}

// Subtract the token out balance from the asset balance
if assetDenom == r.TokenOutDenom {
assetBalance = assetBalance.Sub(tokenInCoin.Amount)
}

normalizationScalingFactor, ok := normalizationFactors[assetDenom]
if !ok {
return fmt.Errorf("normalization scaling factor not found for asset %s, pool id %d", assetDenom, r.GetId())
Expand All @@ -259,34 +257,65 @@ func (r *routableAlloyTransmuterPoolImpl) checkStaticRateLimiter(tokenInCoin sdk
normalizeTotal = normalizeTotal.Add(normalizedBalance)
}

// Calculate weights
// Note: -1 for the alloyed LP share.
weights := make(map[string]osmomath.Dec, len(r.AlloyTransmuterData.AssetConfigs)-1)
for i := 0; i < len(r.AlloyTransmuterData.AssetConfigs); i++ {
assetConfig := r.AlloyTransmuterData.AssetConfigs[i]
assetDenom := assetConfig.Denom

// Skip if the asset is alloyed LP hsare
if strings.Contains(assetDenom, alloyedLPShareDenomComponent) {
continue
// If token in denom is alloyed, we need to validate limiters for all assets' balances except token out.
// Since the token out composition is decreasing, other assets' weights are increasing.
// else, we only need to validate the token in denom limiter.
if tokenInCoin.Denom == r.AlloyTransmuterData.AlloyedDenom {
for i := 0; i < len(r.AlloyTransmuterData.AssetConfigs); i++ {
assetConfig := r.AlloyTransmuterData.AssetConfigs[i]
assetDenom := assetConfig.Denom

// Skip if the asset is alloyed LP share
if assetDenom == r.AlloyTransmuterData.AlloyedDenom {
continue
}

// skip if the asset is token out, since its weight is decreasing, no need to check limiter
if assetDenom == r.TokenOutDenom {
continue
}

// Check if the static rate limiter exists for the asset denom updated balance.
// If not, continue to the next asset
staticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(assetDenom)
if !ok {
continue
}

// Validate upper limit
upperLimitInt := osmomath.MustNewDecFromStr(staticLimiter.UpperLimit)

// Asset weight
assetWeight := normalizedBalances[assetDenom].ToLegacyDec().Quo(normalizeTotal.ToLegacyDec())

// Check the upper limit
if assetWeight.GT(upperLimitInt) {
return domain.StaticRateLimiterInvalidUpperLimitError{
UpperLimit: staticLimiter.UpperLimit,
Weight: assetWeight.String(),
Denom: assetDenom,
}
}
}
} else {
tokeInStaticLimiter, ok := r.AlloyTransmuterData.RateLimiterConfig.GetStaticLimiter(tokenInCoin.Denom)
if !ok {
return nil
}

// Calculate weight
weights[assetDenom] = normalizedBalances[assetDenom].ToLegacyDec().Quo(normalizeTotal.ToLegacyDec())
}

// Validate upper limit
upperLimitInt := osmomath.MustNewDecFromStr(tokeInStaticLimiter.UpperLimit)
// Validate upper limit
upperLimitInt := osmomath.MustNewDecFromStr(tokeInStaticLimiter.UpperLimit)

// Token in weight
tokenInWeight := weights[tokenInCoin.Denom]
// Token in weight
tokenInWeight := normalizedBalances[tokenInCoin.Denom].ToLegacyDec().Quo(normalizeTotal.ToLegacyDec())

// Check the upper limit
if tokenInWeight.GT(upperLimitInt) {
return domain.StaticRateLimiterInvalidUpperLimitError{
UpperLimit: tokeInStaticLimiter.UpperLimit,
Weight: tokenInWeight.String(),
Denom: tokenInCoin.Denom,
// Check the upper limit
if tokenInWeight.GT(upperLimitInt) {
return domain.StaticRateLimiterInvalidUpperLimitError{
UpperLimit: tokeInStaticLimiter.UpperLimit,
Weight: tokenInWeight.String(),
Denom: tokenInCoin.Denom,
}
}
}

Expand Down
32 changes: 26 additions & 6 deletions router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ func (s *RoutablePoolTestSuite) SetupRoutableAlloyTransmuterPoolCustom(tokenInDe
AlloyTransmuter: &cosmwasmpool.AlloyTransmuterData{
AlloyedDenom: ALLUSD,
AssetConfigs: []cosmwasmpool.TransmuterAssetConfig{
{Denom: USDC, NormalizationFactor: osmomath.NewInt(100)},
{Denom: USDT, NormalizationFactor: osmomath.NewInt(1)},
{Denom: OVERLY_PRECISE_USD, NormalizationFactor: veryBigNormalizationFactor},
{Denom: NO_PRECISION_USD, NormalizationFactor: osmomath.ZeroInt()},
{Denom: USDT, NormalizationFactor: osmomath.NewInt(1)},
{Denom: USDC, NormalizationFactor: osmomath.NewInt(100)},
{Denom: ALLUSD, NormalizationFactor: osmomath.NewInt(10)},
},

Expand Down Expand Up @@ -320,6 +320,7 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() {
USDT: osmomath.NewInt(1),
OVERLY_PRECISE_USD: osmomath.NewInt(1),
NO_PRECISION_USD: osmomath.NewInt(1),
ALLUSD: osmomath.NewInt(1),
}

oneInt := osmomath.NewInt(1)
Expand All @@ -337,6 +338,7 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() {

tests := map[string]struct {
tokenInCoin sdk.Coin
tokenOutDenom string
initialBalances sdk.Coins
standardNormFactor osmomath.Int
normalizationScalingFactors map[string]osmomath.Int
Expand All @@ -345,6 +347,7 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() {
}{
"valid token in - below upper limit": {
tokenInCoin: sdk.NewCoin(USDC, osmomath.NewInt(100_000)),
tokenOutDenom: USDT,
initialBalances: defaultInitialBalances,
standardNormFactor: defaultStandardNormFactor,
normalizationScalingFactors: defaultScalingFactors,
Expand All @@ -353,34 +356,51 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() {
},
"invalid token in - exceeds upper limit": {
tokenInCoin: sdk.NewCoin(USDC, osmomath.NewInt(2_000_000)),
tokenOutDenom: USDT,
initialBalances: defaultInitialBalances,
standardNormFactor: defaultStandardNormFactor,
normalizationScalingFactors: defaultScalingFactors,
staticLimiterConfig: defaultStaticLimiterConfig,
expectError: domain.StaticRateLimiterInvalidUpperLimitError{
Denom: USDC,
UpperLimit: "0.5",
Weight: osmomath.MustNewDecFromStr("0.6").String(),
Weight: osmomath.MustNewDecFromStr("1").String(),
},
},
"no static limiter configured": {
tokenInCoin: sdk.NewCoin(USDC, osmomath.NewInt(1_000_000)),
tokenOutDenom: USDT,
initialBalances: defaultInitialBalances,
standardNormFactor: defaultStandardNormFactor,
normalizationScalingFactors: defaultScalingFactors,
staticLimiterConfig: map[string]cosmwasmpool.StaticLimiter{},
expectError: nil,
},
"static limiter not set for token in denom": {
tokenInCoin: sdk.NewCoin(USDC, osmomath.NewInt(1_000_000)),
tokenInCoin: sdk.NewCoin(USDT, osmomath.NewInt(1_000_000)),
tokenOutDenom: USDC,
initialBalances: defaultInitialBalances,
standardNormFactor: defaultStandardNormFactor,
normalizationScalingFactors: defaultScalingFactors,
staticLimiterConfig: defaultStaticLimiterConfig,
expectError: nil,
},
"check all assets' static limiters when token in denom is alloyed": {
tokenInCoin: sdk.NewCoin(ALLUSD, osmomath.NewInt(1_000_001)),
tokenOutDenom: USDT,
initialBalances: defaultInitialBalances,
standardNormFactor: defaultStandardNormFactor,
normalizationScalingFactors: defaultScalingFactors,
staticLimiterConfig: defaultStaticLimiterConfig,
expectError: domain.StaticRateLimiterInvalidUpperLimitError{
Denom: USDC,
UpperLimit: "0.5",
Weight: osmomath.MustNewDecFromStr("0.500000250000125").String(),
},
},
"different normalization factors": {
tokenInCoin: sdk.NewCoin(USDC, osmomath.NewInt(500_000)),
tokenInCoin: sdk.NewCoin(USDC, osmomath.NewInt(500_000)),
tokenOutDenom: USDT,
initialBalances: sdk.NewCoins(
sdk.NewCoin(USDC, osmomath.NewInt(1_000_000)),
sdk.NewCoin(USDT, osmomath.NewInt(2_000_000)),
Expand All @@ -402,7 +422,7 @@ func (s *RoutablePoolTestSuite) TestCheckStaticRateLimiter() {
for name, tc := range tests {
s.Run(name, func() {
s.Setup()
routablePool := s.SetupRoutableAlloyTransmuterPoolCustom(USDT, USDC, tc.initialBalances, osmomath.ZeroDec(), cosmwasmpool.AlloyedRateLimiter{
routablePool := s.SetupRoutableAlloyTransmuterPoolCustom(tc.tokenInCoin.Denom, tc.tokenOutDenom, tc.initialBalances, osmomath.ZeroDec(), cosmwasmpool.AlloyedRateLimiter{
StaticLimiterByDenomMap: tc.staticLimiterConfig,
}, cosmwasmpool.PrecomputedData{
StdNormFactor: tc.standardNormFactor,
Expand Down

0 comments on commit 8362323

Please sign in to comment.