Skip to content

Commit

Permalink
BE-677 | InGivenOut APIs for Alloyed pool (#605)
Browse files Browse the repository at this point in the history
* BE-677 | InGivenOut APIs for Alloyed pool
  • Loading branch information
deividaspetraitis authored Jan 28, 2025
1 parent b410a2a commit bdcbb21
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 11 deletions.
4 changes: 2 additions & 2 deletions domain/mocks/pool_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ func (mp *MockRoutablePool) ChargeTakerFeeExactIn(tokenIn sdk.Coin) (tokenInAfte
}

// ChargeTakerFeeExactOut implements domain.RoutablePool.
func (mp *MockRoutablePool) ChargeTakerFeeExactOut(tokenOut sdk.Coin) (tokenInAfterFee sdk.Coin) {
return tokenOut.Add(sdk.NewCoin(tokenOut.Denom, mp.TakerFee.Mul(tokenOut.Amount.ToLegacyDec()).TruncateInt()))
func (mp *MockRoutablePool) ChargeTakerFeeExactOut(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin) {
return tokenIn.Add(sdk.NewCoin(tokenIn.Denom, mp.TakerFee.Mul(tokenIn.Amount.ToLegacyDec()).TruncateInt()))
}

// GetTakerFee implements ingesttypes.PoolI.
Expand Down
2 changes: 1 addition & 1 deletion domain/routable_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type RoutablePool interface {
CalculateTokenInByTokenOut(ctx context.Context, tokenOut sdk.Coin) (sdk.Coin, error)

ChargeTakerFeeExactIn(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin)
ChargeTakerFeeExactOut(tokenOut sdk.Coin) (tokenOutAfterFee sdk.Coin)
ChargeTakerFeeExactOut(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin)

GetTakerFee() osmomath.Dec

Expand Down
66 changes: 60 additions & 6 deletions router/usecase/pools/routable_cw_alloy_transmuter_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package pools

import (
"context"
"errors"
"fmt"

"cosmossdk.io/math"
Expand Down Expand Up @@ -81,8 +80,30 @@ func (r *routableAlloyTransmuterPoolImpl) CalculateTokenOutByTokenIn(ctx context
}

// CalculateTokenInByTokenOut implements domain.RoutablePool.
func (r *routableAlloyTransmuterPoolImpl) CalculateTokenInByTokenOut(ctx context.Context, tokenOut sdk.Coin) (sdk.Coin, error) {
return sdk.Coin{}, errors.New("not implemented")
// It calculates the amount of token in given the amount of token out for a transmuter pool.
// Transmuter pool allows no slippage swaps. For v3, the ratio of token out to token in is dependent on the normalization factor.
// Returns error if:
// - the underlying chain pool set on the routable pool is not of transmuter type
// - the token out amount is greater than the balance of the token out
// - the token out amount is greater than the balance of the token in
//
// Note that balance validation does not apply to alloyed asset since it can be minted or burned by the pool.
func (r *routableAlloyTransmuterPoolImpl) CalculateTokenInByTokenOut(ctx context.Context, tokenIn sdk.Coin) (sdk.Coin, error) {
tokenInAmt, err := r.CalcTokenInAmt(tokenIn, r.TokenOutDenom)
if err != nil {
return sdk.Coin{}, err
}

tokenInAmtInt := tokenInAmt.Dec().TruncateInt()

// Validate token out balance if not alloyed
if r.TokenInDenom != r.AlloyTransmuterData.AlloyedDenom {
if err := validateTransmuterBalance(tokenInAmtInt, r.Balances, r.TokenInDenom); err != nil {
return sdk.Coin{}, err
}
}

return sdk.Coin{Denom: r.TokenInDenom, Amount: tokenInAmtInt}, nil
}

// GetTokenOutDenom implements RoutablePool.
Expand All @@ -108,9 +129,10 @@ func (r *routableAlloyTransmuterPoolImpl) ChargeTakerFeeExactIn(tokenIn sdk.Coin
}

// ChargeTakerFeeExactOut implements domain.RoutablePool.
// Returns tokenOutAmount and does not charge any fee for transmuter pools.
func (r *routableAlloyTransmuterPoolImpl) ChargeTakerFeeExactOut(tokenOut sdk.Coin) (outAmountAfterFee sdk.Coin) {
return sdk.Coin{}
// Returns tokenInAmount and does not charge any fee for transmuter pools.
func (r *routableAlloyTransmuterPoolImpl) ChargeTakerFeeExactOut(tokenIn sdk.Coin) (inAmountAfterFee sdk.Coin) {
tokenInAfterTakerFee, _ := poolmanager.CalcTakerFeeExactOut(tokenIn, r.GetTakerFee())
return tokenInAfterTakerFee
}

// GetTakerFee implements domain.RoutablePool.
Expand Down Expand Up @@ -208,6 +230,38 @@ func (r *routableAlloyTransmuterPoolImpl) CalcTokenOutAmt(tokenIn sdk.Coin, toke
return tokenOutAmount, nil
}

// Calculate the token in amount based on the normalization factors:
//
// token_in_amt = token_out_amt * token_in_norm_factor / token_out_norm_factor
func (r *routableAlloyTransmuterPoolImpl) CalcTokenInAmt(tokenOut sdk.Coin, tokenInDenom string) (osmomath.BigDec, error) {
tokenInNormFactor, tokenOutNormFactor, err := r.FindNormalizationFactors(tokenInDenom, tokenOut.Denom)
if err != nil {
return osmomath.BigDec{}, err
}

if tokenInNormFactor.IsZero() {
return osmomath.BigDec{}, domain.ZeroNormalizationFactorError{Denom: tokenOut.Denom, PoolId: r.GetId()}
}

if tokenOutNormFactor.IsZero() {
return osmomath.BigDec{}, domain.ZeroNormalizationFactorError{Denom: tokenInDenom, PoolId: r.GetId()}
}

// Check static upper rate limiter
if err := r.checkStaticRateLimiter(tokenOut); err != nil {
return osmomath.BigDec{}, err
}

tokenOutAmount := osmomath.BigDecFromSDKInt(tokenOut.Amount)

tokenOutNormFactorBig := osmomath.NewBigIntFromBigInt(tokenOutNormFactor.BigInt())
tokenInNormFactorBig := osmomath.NewBigIntFromBigInt(tokenInNormFactor.BigInt())

tokenInAmount := tokenOutAmount.MulInt(tokenInNormFactorBig).QuoInt(tokenOutNormFactorBig)

return tokenInAmount, nil
}

// 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.
Expand Down
66 changes: 66 additions & 0 deletions router/usecase/pools/routable_cw_alloy_transmuter_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,72 @@ func (s *RoutablePoolTestSuite) TestCalcTokenOutAmt_AlloyTransmuter() {
}
}

func (s *RoutablePoolTestSuite) TestCalcTokenInAmt_AlloyTransmuter() {
tests := map[string]struct {
tokenOut sdk.Coin
tokenInDenom string
expectedTokenOut osmomath.BigDec
expectedError error
}{
"valid calculation using normalization factors": {
tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(100)),
tokenInDenom: USDT,
expectedTokenOut: osmomath.NewBigDec(1), // (100 * 1) / 100 = 1
expectedError: nil,
},
"valid calculation with decimal points": {
tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(10)),
tokenInDenom: USDT,
expectedTokenOut: osmomath.MustNewBigDecFromStr("0.1"), // (10 * 1) / 100 = 0.1
expectedError: nil,
},
"valid calculation, truncated to zero": {
tokenOut: sdk.NewCoin(OVERLY_PRECISE_USD, osmomath.NewInt(10)),
tokenInDenom: USDC,
expectedTokenOut: osmomath.MustNewBigDecFromStr("0"),
expectedError: nil,
},
"missing normalization factor for token in": {
tokenOut: sdk.NewCoin(INVALID_DENOM, osmomath.NewInt(100)),
tokenInDenom: USDT,
expectedTokenOut: osmomath.BigDec{},
expectedError: domain.MissingNormalizationFactorError{Denom: INVALID_DENOM, PoolId: defaultPoolID},
},
"missing normalization factor for token out": {
tokenOut: sdk.NewCoin(USDC, osmomath.NewInt(100)),
tokenInDenom: INVALID_DENOM,
expectedTokenOut: osmomath.BigDec{},
expectedError: domain.MissingNormalizationFactorError{Denom: INVALID_DENOM, PoolId: defaultPoolID},
},
"missing normalization factors for both token in and token out": {
tokenOut: sdk.NewCoin(INVALID_DENOM, osmomath.NewInt(100)),
tokenInDenom: INVALID_DENOM,
expectedTokenOut: osmomath.BigDec{},
expectedError: domain.MissingNormalizationFactorError{Denom: INVALID_DENOM, PoolId: defaultPoolID},
},
}

for name, tc := range tests {
s.Run(name, func() {
s.Setup()

routablePool := s.SetupRoutableAlloyTransmuterPool(tc.tokenInDenom, tc.tokenOut.Denom, sdk.Coins{}, osmomath.ZeroDec())

r := routablePool.(*pools.RoutableAlloyTransmuterPoolImpl)

tokenIn, err := r.CalcTokenInAmt(tc.tokenOut, tc.tokenInDenom)

if tc.expectedError != nil {
s.Require().Error(err)
s.Require().ErrorIs(err, tc.expectedError)
} else {
s.Require().NoError(err)
s.Require().Equal(tc.expectedTokenOut, tokenIn)
}
})
}
}

func (s *RoutablePoolTestSuite) TestChargeTakerFeeExactIn_AlloyTransmuter() {
tests := map[string]struct {
tokenIn sdk.Coin
Expand Down
2 changes: 1 addition & 1 deletion router/usecase/pools/routable_cw_orderbook_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (r *routableOrderbookPoolImpl) ChargeTakerFeeExactIn(tokenIn sdk.Coin) (tok

// ChargeTakerFee implements sqsdomain.RoutablePool.
// Charges the taker fee for the given token out and returns the token out after the fee has been charged.
func (r *routableOrderbookPoolImpl) ChargeTakerFeeExactOut(tokenOut sdk.Coin) (tokenOutAfterFee sdk.Coin) {
func (r *routableOrderbookPoolImpl) ChargeTakerFeeExactOut(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin) {
return sdk.Coin{}
}

Expand Down
2 changes: 1 addition & 1 deletion router/usecase/pools/routable_result_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (r *routableResultPoolImpl) ChargeTakerFeeExactIn(tokenIn sdk.Coin) (tokenI

// ChargeTakerFee implements domain.RoutablePool.
// Charges the taker fee for the given token out and returns the token out after the fee has been charged.
func (r *routableResultPoolImpl) ChargeTakerFeeExactOut(tokenOut sdk.Coin) (tokenOutAfterFee sdk.Coin) {
func (r *routableResultPoolImpl) ChargeTakerFeeExactOut(tokenIn sdk.Coin) (tokenInAfterFee sdk.Coin) {
return sdk.Coin{}
}

Expand Down

0 comments on commit bdcbb21

Please sign in to comment.