Skip to content

Commit

Permalink
added unit tests for cctx value conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
ws4charlie committed Apr 20, 2024
1 parent 60e5dcc commit 101457c
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 41 deletions.
6 changes: 3 additions & 3 deletions testutil/keeper/mocks/crosschain/fungible.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 27 additions & 28 deletions x/crosschain/keeper/grpc_query_cctx_rate_limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ func (k Keeper) ListPendingCctxWithinRateLimit(c context.Context, req *types.Que
// get the conversion rates for all foreign coins
var gasCoinRates map[int64]sdk.Dec
var erc20CoinRates map[int64]map[string]sdk.Dec
var erc20Coins map[int64]map[string]fungibletypes.ForeignCoins
var foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins
var rateLimitInZeta sdk.Dec
if applyLimit {
gasCoinRates, erc20CoinRates = k.GetRateLimiterRates(ctx)
erc20Coins = k.fungibleKeeper.GetAllForeignERC20CoinMap(ctx)
foreignCoinMap = k.fungibleKeeper.GetAllForeignCoinMap(ctx)
rateLimitInZeta = sdk.NewDecFromBigInt(rateLimitFlags.Rate.BigInt())
}

Expand Down Expand Up @@ -130,7 +130,7 @@ LoopBackwards:
break
}
// criteria #2: we should finish the RPC call if the rate limit is exceeded
if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, erc20Coins, &totalCctxValueInZeta, rateLimitInZeta) {
if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap, &totalCctxValueInZeta, rateLimitInZeta) {
limitExceeded = true
break LoopBackwards
}
Expand Down Expand Up @@ -167,7 +167,7 @@ LoopForwards:
break LoopForwards
}
// criteria #2: we should finish the RPC call if the rate limit is exceeded
if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, erc20Coins, &totalCctxValueInZeta, rateLimitInZeta) {
if rateLimitExceeded(chain.ChainId, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap, &totalCctxValueInZeta, rateLimitInZeta) {
limitExceeded = true
break LoopForwards
}
Expand All @@ -182,39 +182,26 @@ LoopForwards:
}, nil
}

// convertCctxValue converts the value of the cctx in ZETA using given conversion rates
func convertCctxValue(
// ConvertCctxValue converts the value of the cctx in ZETA using given conversion rates
func ConvertCctxValue(
chainID int64,
cctx *types.CrossChainTx,
gasCoinRates map[int64]sdk.Dec,
erc20CoinRates map[int64]map[string]sdk.Dec,
erc20Coins map[int64]map[string]fungibletypes.ForeignCoins,
foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins,
) sdk.Dec {
var rate sdk.Dec
var decimals uint64
switch cctx.InboundTxParams.CoinType {
case coin.CoinType_Zeta:
// no conversion needed for ZETA
rate = sdk.NewDec(1)
amountCctx := sdk.NewDecFromBigInt(cctx.GetCurrentOutTxParam().Amount.BigInt())
return amountCctx.Quo(sdk.NewDec(10).Power(18))
case coin.CoinType_Gas:
rate = gasCoinRates[chainID]
case coin.CoinType_ERC20:
// get the ERC20 coin decimals
_, found := erc20Coins[chainID]
if !found {
// skip if no coin found for this chainID
return sdk.NewDec(0)
}
fCoin, found := erc20Coins[chainID][strings.ToLower(cctx.InboundTxParams.Asset)]
if !found {
// skip if no coin found for this Asset
return sdk.NewDec(0)
}
// #nosec G701 always in range
decimals = uint64(fCoin.Decimals)

// get the ERC20 coin rate
_, found = erc20CoinRates[chainID]
_, found := erc20CoinRates[chainID]
if !found {
// skip if no rate found for this chainID
return sdk.NewDec(0)
Expand All @@ -224,16 +211,28 @@ func convertCctxValue(
// skip CoinType_Cmd
return sdk.NewDec(0)
}

// should not happen, return 0 to skip if it happens
if rate.LTE(sdk.NewDec(0)) {
if rate.IsNil() || rate.LTE(sdk.NewDec(0)) {
return sdk.NewDec(0)
}

// get foreign coin decimals
_, found := foreignCoinMap[chainID]
if !found {
// skip if no coin found for this chainID
return sdk.NewDec(0)
}
fCoin, found := foreignCoinMap[chainID][strings.ToLower(cctx.InboundTxParams.Asset)]
if !found {
// skip if no coin found for this Asset
return sdk.NewDec(0)
}
decimals = uint64(fCoin.Decimals)

// the reciprocal of `rate` is the amount of zrc20 needed to buy 1 ZETA
// for example, given rate = 0.8, the reciprocal is 1.25, which means 1.25 ZRC20 can buy 1 ZETA
// given decimals = 6, the `oneZeta` amount will be 1.25 * 10^6 = 1250000
oneZrc20 := sdk.NewDec(1).Power(decimals)
oneZrc20 := sdk.NewDec(10).Power(decimals)
oneZeta := oneZrc20.Quo(rate)

// convert asset amount into ZETA
Expand All @@ -249,11 +248,11 @@ func rateLimitExceeded(
cctx *types.CrossChainTx,
gasCoinRates map[int64]sdk.Dec,
erc20CoinRates map[int64]map[string]sdk.Dec,
erc20Coins map[int64]map[string]fungibletypes.ForeignCoins,
foreignCoinMap map[int64]map[string]fungibletypes.ForeignCoins,
currentCctxValue *sdk.Dec,
rateLimitValue sdk.Dec,
) bool {
amountZeta := convertCctxValue(chainID, cctx, gasCoinRates, erc20CoinRates, erc20Coins)
amountZeta := ConvertCctxValue(chainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap)
*currentCctxValue = currentCctxValue.Add(amountZeta)
return currentCctxValue.GT(rateLimitValue)
}
141 changes: 141 additions & 0 deletions x/crosschain/keeper/grpc_query_cctx_rate_limit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper_test
import (
"fmt"
"sort"
"strings"
"testing"

"cosmossdk.io/math"
Expand Down Expand Up @@ -123,6 +124,146 @@ func setupForeignCoins(
}
}

func Test_ConvertCctxValue(t *testing.T) {
// chain IDs
ethChainID := getValidEthChainID()
btcChainID := getValidBtcChainID()

// zrc20 addresses for ETH, BTC, USDT and asset for USDT
zrc20ETH := sample.EthAddress().Hex()
zrc20BTC := sample.EthAddress().Hex()
zrc20USDT := sample.EthAddress().Hex()
assetUSDT := sample.EthAddress().Hex()

k, ctx, _, zk := keepertest.CrosschainKeeper(t)

// Set TSS
tss := sample.Tss()
zk.ObserverKeeper.SetTSS(ctx, tss)

// Set foreign coins
setupForeignCoins(t, ctx, zk, zrc20ETH, zrc20BTC, zrc20USDT, assetUSDT)

// Set rate limiter flags
rateLimiterFlags := createTestRateLimiterFlags(zrc20ETH, zrc20BTC, zrc20USDT, "2500", "50000", "0.8")
k.SetRateLimiterFlags(ctx, rateLimiterFlags)

// get rate limiter rates
gasCoinRates, erc20CoinRates := k.GetRateLimiterRates(ctx)
foreignCoinMap := zk.FungibleKeeper.GetAllForeignCoinMap(ctx)

t.Run("should convert cctx ZETA value correctly", func(t *testing.T) {
// create cctx with 0.3 ZETA
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_Zeta
cctx.InboundTxParams.Asset = ""
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e17) // 0.3 ZETA

// convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.MustNewDecFromStr("0.3"), value)
})
t.Run("should convert cctx ETH value correctly", func(t *testing.T) {
// create cctx with 0.003 ETH
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_Gas
cctx.InboundTxParams.Asset = ""
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e15) // 0.003 ETH

// convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.MustNewDecFromStr("7.5"), value)
})
t.Run("should convert cctx BTC value correctly", func(t *testing.T) {
// create cctx with 0.0007 BTC
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", btcChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_Gas
cctx.InboundTxParams.Asset = ""
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(70000) // 0.0007 BTC

// convert cctx value
value := keeper.ConvertCctxValue(btcChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.MustNewDecFromStr("35.0"), value)
})
t.Run("should convert cctx USDT value correctly", func(t *testing.T) {
// create cctx with 3 USDT
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_ERC20
cctx.InboundTxParams.Asset = assetUSDT
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(3e6) // 3 USDT

// convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.MustNewDecFromStr("2.4"), value)
})
t.Run("should return 0 if no rate found for chainID", func(t *testing.T) {
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_ERC20
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100)

// use nil erc20CoinRates map to convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, nil, foreignCoinMap)
require.Equal(t, sdk.NewDec(0), value)
})
t.Run("should return 0 if coinType is CoinType_Cmd", func(t *testing.T) {
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_Cmd
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100)

// convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.NewDec(0), value)
})
t.Run("should return 0 on nil rate or rate <= 0", func(t *testing.T) {
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_Gas
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100)

// use nil gasCoinRates map to convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, nil, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.NewDec(0), value)

// set rate to 0
zeroCoinRates, _ := k.GetRateLimiterRates(ctx)
zeroCoinRates[ethChainID] = sdk.NewDec(0)

// convert cctx value
value = keeper.ConvertCctxValue(ethChainID, cctx, zeroCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.NewDec(0), value)

// set rate to -1
negativeCoinRates, _ := k.GetRateLimiterRates(ctx)
negativeCoinRates[ethChainID] = sdk.NewDec(-1)

// convert cctx value
value = keeper.ConvertCctxValue(ethChainID, cctx, negativeCoinRates, erc20CoinRates, foreignCoinMap)
require.Equal(t, sdk.NewDec(0), value)
})
t.Run("should return 0 if no coin found for chainID", func(t *testing.T) {
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_Gas
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100)

// use empty foreignCoinMap to convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, nil)
require.Equal(t, sdk.NewDec(0), value)
})
t.Run("should return 0 if no coin found for asset", func(t *testing.T) {
cctx := sample.CrossChainTx(t, fmt.Sprintf("%d-%d", ethChainID, 1))
cctx.InboundTxParams.CoinType = coin.CoinType_ERC20
cctx.InboundTxParams.Asset = assetUSDT
cctx.GetCurrentOutTxParam().Amount = sdk.NewUint(100)

// delete assetUSDT from foreignCoinMap for ethChainID
tempCoinMap := zk.FungibleKeeper.GetAllForeignCoinMap(ctx)
delete(tempCoinMap[ethChainID], strings.ToLower(assetUSDT))

// convert cctx value
value := keeper.ConvertCctxValue(ethChainID, cctx, gasCoinRates, erc20CoinRates, tempCoinMap)
require.Equal(t, sdk.NewDec(0), value)
})
}

func TestKeeper_ListPendingCctxWithinRateLimit(t *testing.T) {
// chain IDs
ethChainID := getValidEthChainID()
Expand Down
2 changes: 1 addition & 1 deletion x/crosschain/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ type ObserverKeeper interface {
type FungibleKeeper interface {
GetForeignCoins(ctx sdk.Context, zrc20Addr string) (val fungibletypes.ForeignCoins, found bool)
GetAllForeignCoins(ctx sdk.Context) (list []fungibletypes.ForeignCoins)
GetAllForeignERC20CoinMap(ctx sdk.Context) map[int64]map[string]fungibletypes.ForeignCoins
GetAllForeignCoinMap(ctx sdk.Context) map[int64]map[string]fungibletypes.ForeignCoins
SetForeignCoins(ctx sdk.Context, foreignCoins fungibletypes.ForeignCoins)
GetAllForeignCoinsForChain(ctx sdk.Context, foreignChainID int64) (list []fungibletypes.ForeignCoins)
GetForeignCoinFromAsset(ctx sdk.Context, asset string, chainID int64) (fungibletypes.ForeignCoins, bool)
Expand Down
16 changes: 7 additions & 9 deletions x/fungible/keeper/foreign_coins.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,18 @@ func (k Keeper) GetAllForeignCoins(ctx sdk.Context) (list []types.ForeignCoins)
return
}

// GetAllForeignERC20CoinMap returns all foreign ERC20 coins in a map of chainID -> asset -> coin
func (k Keeper) GetAllForeignERC20CoinMap(ctx sdk.Context) map[int64]map[string]types.ForeignCoins {
// GetAllForeignCoinMap returns all foreign ERC20 coins in a map of chainID -> asset -> coin
func (k Keeper) GetAllForeignCoinMap(ctx sdk.Context) map[int64]map[string]types.ForeignCoins {
allForeignCoins := k.GetAllForeignCoins(ctx)

erc20CoinMap := make(map[int64]map[string]types.ForeignCoins)
fCoinMap := make(map[int64]map[string]types.ForeignCoins)
for _, c := range allForeignCoins {
if c.CoinType == coin.CoinType_ERC20 {
if _, found := erc20CoinMap[c.ForeignChainId]; !found {
erc20CoinMap[c.ForeignChainId] = make(map[string]types.ForeignCoins)
}
erc20CoinMap[c.ForeignChainId][strings.ToLower(c.Asset)] = c
if _, found := fCoinMap[c.ForeignChainId]; !found {
fCoinMap[c.ForeignChainId] = make(map[string]types.ForeignCoins)
}
fCoinMap[c.ForeignChainId][strings.ToLower(c.Asset)] = c
}
return erc20CoinMap
return fCoinMap
}

// GetGasCoinForForeignCoin returns the gas coin for a given chain
Expand Down

0 comments on commit 101457c

Please sign in to comment.