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

feat: multi-network upgrade handler #5959

Merged
merged 48 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7ec3d77
multi-network upgrade handler
czarcas7ic Aug 4, 2023
3e0650a
run mainnet upgrade handler for e2e network IDs
czarcas7ic Aug 4, 2023
c697de2
fixes
czarcas7ic Aug 4, 2023
bbb644b
fix
czarcas7ic Aug 4, 2023
ab52240
widen tolerance
czarcas7ic Aug 4, 2023
da6324c
more resilient way to calculate coins used
czarcas7ic Aug 4, 2023
f849825
add logs to upgrade handler
czarcas7ic Aug 4, 2023
d70423c
scale error tolerance with spread factor
czarcas7ic Aug 4, 2023
7145a25
change back init tag
czarcas7ic Aug 4, 2023
146faa5
update changelog
devbot-wizard Aug 5, 2023
8049092
Merge branch 'main' into adam/multi-network-upgrade-handler
devbot-wizard Aug 5, 2023
7d7695b
Fix imports
devbot-wizard Aug 5, 2023
b334722
check liquidity prior to creating pool
czarcas7ic Aug 5, 2023
bdb5741
check base/quote rather than all denoms
czarcas7ic Aug 5, 2023
2d02cea
check if pool only has 2 assets
czarcas7ic Aug 6, 2023
a1f3bbe
skip pools that are already linked
czarcas7ic Aug 7, 2023
3a21bd5
add log and fix issue
czarcas7ic Aug 7, 2023
cbedf7a
add an additional log
czarcas7ic Aug 7, 2023
d0eba58
more logs
czarcas7ic Aug 7, 2023
1454538
more prints
czarcas7ic Aug 7, 2023
f04b9c8
skip if incentive gauge duration does not exist
czarcas7ic Aug 7, 2023
3ce1f41
use gogotypes
czarcas7ic Aug 7, 2023
9318fd8
use store.Has
czarcas7ic Aug 7, 2023
cf87cde
error instead of panic on getgaugeId from poolId
czarcas7ic Aug 7, 2023
b9f28f3
Update app/upgrades/v17/upgrades_test.go
czarcas7ic Aug 7, 2023
d793a36
remove extra prints
czarcas7ic Aug 7, 2023
7cdf2d3
Merge branch 'adam/multi-network-upgrade-handler' of https://github.c…
czarcas7ic Aug 7, 2023
68cd773
remove old logs
czarcas7ic Aug 7, 2023
1cfec91
Merge branch 'main' into adam/multi-network-upgrade-handler
czarcas7ic Aug 7, 2023
c8aa647
Merge branch 'main' into adam/multi-network-upgrade-handler
czarcas7ic Aug 7, 2023
449d9a2
enable superfluid assets both chains
czarcas7ic Aug 7, 2023
da5d1ee
remove unused func
czarcas7ic Aug 7, 2023
bedf1a5
setting the default ibc-hooks params
nicolaslara Aug 8, 2023
77616de
Merge branch 'main' into adam/multi-network-upgrade-handler
czarcas7ic Aug 8, 2023
0c2b1e7
separate method for enable superfluid for testnet
czarcas7ic Aug 8, 2023
34cff15
drastically simplify the upgrade handler
czarcas7ic Aug 8, 2023
1801232
check superfluid error type directly
czarcas7ic Aug 8, 2023
3cd0af5
remove use of pointer of coinsUsed and poolLinks
czarcas7ic Aug 8, 2023
168cece
Merge branch 'main' into adam/multi-network-upgrade-handler
czarcas7ic Aug 9, 2023
85abff8
Update app/upgrades/v17/upgrades.go
p0mvn Aug 9, 2023
27e07ca
Update app/upgrades/v17/upgrades.go
p0mvn Aug 9, 2023
d400d36
add spot price check when adding testnet assets
czarcas7ic Aug 9, 2023
2886d7a
fix osmo amount
czarcas7ic Aug 9, 2023
d778f12
add error check
czarcas7ic Aug 9, 2023
ecbb383
Merge branch 'main' into adam/multi-network-upgrade-handler
czarcas7ic Aug 9, 2023
5fdb415
add additional check from testnet to mainnet
czarcas7ic Aug 9, 2023
ffd220d
remove this check from mainnet handler
czarcas7ic Aug 9, 2023
4b63216
fix lingering merge conflict
czarcas7ic Aug 9, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#5923] (https://github.com/osmosis-labs/osmosis/pull/5923) CL: Lower gas for initializing ticks
* [#5927] (https://github.com/osmosis-labs/osmosis/pull/5927) Add gas metering to x/tokenfactory trackBeforeSend hook
* [#5890](https://github.com/osmosis-labs/osmosis/pull/5890) feat: CreateCLPool & LinkCFMMtoCL pool into one gov-prop
* [#5959](https://github.com/osmosis-labs/osmosis/pull/5959) allow testing with different chain-id's in E2E testing
* [#5964](https://github.com/osmosis-labs/osmosis/pull/5964) fix e2e test concurrency bugs
* [#5948] (https://github.com/osmosis-labs/osmosis/pull/5948) Parameterizing Pool Type Information in Protorev

Expand Down
312 changes: 247 additions & 65 deletions app/upgrades/v17/upgrades.go
Original file line number Diff line number Diff line change
@@ -1,105 +1,66 @@
package v17

import (
"errors"
"fmt"
"time"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"

cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types"
gammtypes "github.com/osmosis-labs/osmosis/v17/x/gamm/types"
gammmigration "github.com/osmosis-labs/osmosis/v17/x/gamm/types/migration"
poolManagerTypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types"
superfluidtypes "github.com/osmosis-labs/osmosis/v17/x/superfluid/types"

"github.com/osmosis-labs/osmosis/v17/app/keepers"
"github.com/osmosis-labs/osmosis/v17/app/upgrades"
"github.com/osmosis-labs/osmosis/v17/x/protorev/types"
)

const (
mainnetChainID = "osmosis-1"
e2eChainA = "osmo-test-a"
e2eChainB = "osmo-test-b"
)

var notEnoughLiquidityForSwapErr = errorsmod.Wrapf(gammtypes.ErrInvalidMathApprox, "token amount must be positive")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why don't we import this error from the place where it is returned in superfluid?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no error to import. Its from gamm, and every instance of this is manually entered. Example:

return sdk.Int{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "token amount must be positive")
}

return sdk.Coin{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "token amount must be positive")
}

return sdk.Coin{}, errorsmod.Wrapf(types.ErrInvalidMathApprox, "token amount must be positive")
}


func CreateUpgradeHandler(
mm *module.Manager,
configurator module.Configurator,
bpm upgrades.BaseAppParamManager,
keepers *keepers.AppKeepers,
) upgradetypes.UpgradeHandler {
return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
poolLinks := []gammmigration.BalancerToConcentratedPoolLink{}
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
fullRangeCoinsUsed := sdk.Coins{}

// Run migrations before applying any other state changes.
// NOTE: DO NOT PUT ANY STATE CHANGES BEFORE RunMigrations().
migrations, err := mm.RunMigrations(ctx, configurator, fromVM)
if err != nil {
return nil, err
}

// Get community pool address.
communityPoolAddress := keepers.AccountKeeper.GetModuleAddress(distrtypes.ModuleName)

// fullRangeCoinsUsed tracks the coins we use in the below for loop from the community pool to create the full range position for each new pool.
fullRangeCoinsUsed := sdk.NewCoins()

poolLinks := []gammmigration.BalancerToConcentratedPoolLink{}

assetPairs := InitializeAssetPairs(ctx, keepers)

for _, assetPair := range assetPairs {
// Create a concentrated liquidity pool for asset pair.
clPool, err := keepers.GAMMKeeper.CreateConcentratedPoolFromCFMM(ctx, assetPair.LinkedClassicPool, assetPair.BaseAsset, assetPair.SpreadFactor, TickSpacing)
if err != nil {
return nil, err
}
clPoolId := clPool.GetId()
clPoolDenom := cltypes.GetConcentratedLockupDenomFromPoolId(clPoolId)

// Add the pool link to the list of pool links (we set them all at once later)
poolLinks = append(poolLinks, gammmigration.BalancerToConcentratedPoolLink{
BalancerPoolId: assetPair.LinkedClassicPool,
ClPoolId: clPoolId,
})

// Determine the amount of baseAsset that can be bought with 1 OSMO.
oneOsmo := sdk.NewCoin(QuoteAsset, sdk.NewInt(1000000))
linkedClassicPool, err := keepers.PoolManagerKeeper.GetPool(ctx, assetPair.LinkedClassicPool)
if err != nil {
return nil, err
}
respectiveBaseAsset, err := keepers.GAMMKeeper.CalcOutAmtGivenIn(ctx, linkedClassicPool, oneOsmo, assetPair.BaseAsset, sdk.ZeroDec())
if err != nil {
return nil, err
}

// Create a full range position via the community pool with the funds we calculated above.
fullRangeCoins := sdk.NewCoins(respectiveBaseAsset, oneOsmo)
_, actualBaseAmtUsed, actualQuoteAmtUsed, _, err := keepers.ConcentratedLiquidityKeeper.CreateFullRangePosition(ctx, clPoolId, communityPoolAddress, fullRangeCoins)
if err != nil {
return nil, err
}

// Track the coins used to create the full range position (we manually update the fee pool later all at once).
fullRangeCoinsUsed = fullRangeCoinsUsed.Add(sdk.NewCoins(sdk.NewCoin(QuoteAsset, actualQuoteAmtUsed), sdk.NewCoin(assetPair.BaseAsset, actualBaseAmtUsed))...)

// If pair was previously superfluid enabled, add the cl pool's full range denom as an authorized superfluid asset.
if assetPair.Superfluid {
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
superfluidAsset := superfluidtypes.SuperfluidAsset{
Denom: clPoolDenom,
AssetType: superfluidtypes.SuperfluidAssetTypeConcentratedShare,
}
err = keepers.SuperfluidKeeper.AddNewSuperfluidAsset(ctx, superfluidAsset)
if err != nil {
return nil, err
}
}

clPoolTwapRecords, err := keepers.TwapKeeper.GetAllMostRecentRecordsForPool(ctx, clPoolId)
if err != nil {
return nil, err
}

for _, twapRecord := range clPoolTwapRecords {
twapRecord.LastErrorTime = time.Time{}
keepers.TwapKeeper.StoreNewRecord(ctx, twapRecord)
}
if ctx.ChainID() == mainnetChainID || ctx.ChainID() == e2eChainA || ctx.ChainID() == e2eChainB {
// Upgrades specific balancer pools to concentrated liquidity pools and links them to their CL equivalent.
ctx.Logger().Info(fmt.Sprintf("Chain ID is %s, running mainnet upgrade handler", ctx.ChainID()))
err = mainnetUpgradeHandler(ctx, keepers, communityPoolAddress, &poolLinks, &fullRangeCoinsUsed)
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Upgrades all existing balancer pools to concentrated liquidity pools and links them to their CL equivalent.
ctx.Logger().Info(fmt.Sprintf("Chain ID is %s, running testnet upgrade handler", ctx.ChainID()))
err = testnetUpgradeHandler(ctx, keepers, communityPoolAddress, &poolLinks, &fullRangeCoinsUsed)
}
if err != nil {
return nil, err
}

// Set the migration links in x/gamm.
Expand Down Expand Up @@ -128,3 +89,224 @@ func CreateUpgradeHandler(
return migrations, nil
}
}

// mainnetUpgradeHandler creates CL pools for all balancer pools defined in the asset pairs struct. It also links the CL pools to their balancer pool counterpart, creates a full range position with the community pool,
// authorizes superfluid for the CL pool if the balancer pool is superfluid enabled, and manually sets the TWAP records for the CL pool.
func mainnetUpgradeHandler(ctx sdk.Context, keepers *keepers.AppKeepers, communityPoolAddress sdk.AccAddress, poolLinks *[]gammmigration.BalancerToConcentratedPoolLink, fullRangeCoinsUsed *sdk.Coins) error {
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
assetPairs := InitializeAssetPairs(ctx, keepers)

for _, assetPair := range assetPairs {
clPoolDenom, clPoolId, err := createCLPoolWithCommunityPoolPosition(ctx, keepers, assetPair.LinkedClassicPool, assetPair.BaseAsset, assetPair.SpreadFactor, communityPoolAddress, poolLinks, fullRangeCoinsUsed)
if err != nil {
return err
}

err = authorizeSuperfluidIfEnabled(ctx, keepers, assetPair.LinkedClassicPool, clPoolDenom)
if err != nil {
return err
}

err = manuallySetTWAPRecords(ctx, keepers, clPoolId)
if err != nil {
return err
}
}

return nil
}

// testnetUpgradeHandler creates CL pools for all existing balancer pools. It also links the CL pools to their balancer pool counterpart, creates a full range position with the community pool,
// authorizes superfluid for the CL pool if the balancer pool is superfluid enabled, and manually sets the TWAP records for the CL pool.
func testnetUpgradeHandler(ctx sdk.Context, keepers *keepers.AppKeepers, communityPoolAddress sdk.AccAddress, poolLinks *[]gammmigration.BalancerToConcentratedPoolLink, fullRangeCoinsUsed *sdk.Coins) error {
// Retrieve all GAMM pools on the testnet.
pools, err := keepers.GAMMKeeper.GetPools(ctx)
if err != nil {
return err
}

for _, pool := range pools {
skipPool, gammPoolId, baseAsset, spreadFactor, err := testnetParsePoolRecord(ctx, pool, keepers)
if err != nil {
return err
}
if skipPool {
continue
}

clPoolDenom, clPoolId, err := createCLPoolWithCommunityPoolPosition(ctx, keepers, gammPoolId, baseAsset, spreadFactor, communityPoolAddress, poolLinks, fullRangeCoinsUsed)
if errors.Is(err, notEnoughLiquidityForSwapErr) {
continue
} else if err != nil {
return err
}

err = authorizeSuperfluidIfEnabled(ctx, keepers, gammPoolId, clPoolDenom)
if err != nil {
return err
}

err = manuallySetTWAPRecords(ctx, keepers, clPoolId)
if err != nil {
return err
}
}
return nil
}

// createCLPoolWithCommunityPoolPosition creates a CL pool for a given balancer pool and adds a full range position with the community pool.
func createCLPoolWithCommunityPoolPosition(ctx sdk.Context, keepers *keepers.AppKeepers, gammPoolId uint64, baseAsset string, spreadFactor sdk.Dec, communityPoolAddress sdk.AccAddress, poolLinks *[]gammmigration.BalancerToConcentratedPoolLink, fullRangeCoinsUsed *sdk.Coins) (clPoolDenom string, clPoolId uint64, err error) {
// Check if classic pool has enough liquidity to support a 0.1 OSMO swap before creating a CL pool.
// If not, skip the pool.
osmoIn := sdk.NewCoin(QuoteAsset, sdk.NewInt(100000))
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
linkedClassicPool, err := keepers.PoolManagerKeeper.GetPool(ctx, gammPoolId)
if err != nil {
return "", 0, err
}
_, err = keepers.GAMMKeeper.CalcOutAmtGivenIn(ctx, linkedClassicPool, osmoIn, baseAsset, spreadFactor)
if err != nil {
return "", 0, err
}

// Create a concentrated liquidity pool for asset pair.
ctx.Logger().Info(fmt.Sprintf("Creating CL pool from poolID (%d), baseAsset (%s), spreadFactor (%s), tickSpacing (%d)", gammPoolId, baseAsset, spreadFactor, TickSpacing))
clPool, err := keepers.GAMMKeeper.CreateConcentratedPoolFromCFMM(ctx, gammPoolId, baseAsset, spreadFactor, TickSpacing)
if err != nil {
return "", 0, err
}
clPoolId = clPool.GetId()
clPoolDenom = cltypes.GetConcentratedLockupDenomFromPoolId(clPoolId)

// Add the pool link to the list of pool links (we set them all at once later)
*poolLinks = append(*poolLinks, gammmigration.BalancerToConcentratedPoolLink{
BalancerPoolId: gammPoolId,
ClPoolId: clPoolId,
})

// Get community pool balance before swap and position creation
commPoolBalanceBaseAssetPre := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, baseAsset)
commPoolBalanceQuoteAssetPre := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, QuoteAsset)
commPoolBalancePre := sdk.NewCoins(commPoolBalanceBaseAssetPre, commPoolBalanceQuoteAssetPre)

// Swap 0.1 OSMO for baseAsset from the community pool.
respectiveBaseAssetInt, err := keepers.GAMMKeeper.SwapExactAmountIn(ctx, communityPoolAddress, linkedClassicPool, osmoIn, baseAsset, sdk.ZeroInt(), linkedClassicPool.GetSpreadFactor(ctx))
if err != nil {
return "", 0, err
}
ctx.Logger().Info(fmt.Sprintf("Swapped %s for %s%s from the community pool", osmoIn.String(), respectiveBaseAssetInt.String(), baseAsset))

respectiveBaseAsset := sdk.NewCoin(baseAsset, respectiveBaseAssetInt)

// Create a full range position via the community pool with the funds we calculated above.
fullRangeCoins := sdk.NewCoins(respectiveBaseAsset, osmoIn)
_, _, _, _, err = keepers.ConcentratedLiquidityKeeper.CreateFullRangePosition(ctx, clPoolId, communityPoolAddress, fullRangeCoins)
if err != nil {
return "", 0, err
}

// Get community pool balance after swap and position creation
commPoolBalanceBaseAssetPost := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, baseAsset)
commPoolBalanceQuoteAssetPost := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, QuoteAsset)
commPoolBalancePost := sdk.NewCoins(commPoolBalanceBaseAssetPost, commPoolBalanceQuoteAssetPost)

// While we can be fairly certain the diff between these two is 0.2 OSMO, if for whatever reason
// some baseAsset dust remains in the community pool and we don't account for it, when updating the
// fee pool balance later, we will be off by that amount and will cause a panic.
coinsUsed := commPoolBalancePre.Sub(commPoolBalancePost)

// Track the coins used to create the full range position (we manually update the fee pool later all at once).
*fullRangeCoinsUsed = fullRangeCoinsUsed.Add(coinsUsed...)

return clPoolDenom, clPoolId, nil
}

// authorizeSuperfluidIfEnabled authorizes superfluid for a CL pool if the balancer pool is superfluid enabled.
func authorizeSuperfluidIfEnabled(ctx sdk.Context, keepers *keepers.AppKeepers, gammPoolId uint64, clPoolDenom string) (err error) {
// If pair was previously superfluid enabled, add the cl pool's full range denom as an authorized superfluid asset.
poolShareDenom := fmt.Sprintf("gamm/pool/%d", gammPoolId)
_, err = keepers.SuperfluidKeeper.GetSuperfluidAsset(ctx, poolShareDenom)
if err == nil {
ctx.Logger().Info(fmt.Sprintf("%s is superfluid enabled, enabling %s as a superfluid asset", poolShareDenom, clPoolDenom))
superfluidAsset := superfluidtypes.SuperfluidAsset{
Denom: clPoolDenom,
AssetType: superfluidtypes.SuperfluidAssetTypeConcentratedShare,
}
err = keepers.SuperfluidKeeper.AddNewSuperfluidAsset(ctx, superfluidAsset)
if err != nil {
return err
}
}
ctx.Logger().Info(fmt.Sprintf("%s is not superfluid enabled, not enabling %s as a superfluid asset", poolShareDenom, clPoolDenom))
return nil
czarcas7ic marked this conversation as resolved.
Show resolved Hide resolved
}

// manuallySetTWAPRecords manually sets the TWAP records for a CL pool. This prevents a panic when the CL pool is first used.
func manuallySetTWAPRecords(ctx sdk.Context, keepers *keepers.AppKeepers, clPoolId uint64) error {
ctx.Logger().Info(fmt.Sprintf("manually setting twap record for newly created CL poolID %d", clPoolId))
clPoolTwapRecords, err := keepers.TwapKeeper.GetAllMostRecentRecordsForPool(ctx, clPoolId)
if err != nil {
return err
}

for _, twapRecord := range clPoolTwapRecords {
twapRecord.LastErrorTime = time.Time{}
keepers.TwapKeeper.StoreNewRecord(ctx, twapRecord)
}
return nil
}

// testnetParsePoolRecord parses a pool record and returns whether or not to skip the pool, the pool's gammPoolId, the pool's base asset, and the pool's spread factor.
func testnetParsePoolRecord(ctx sdk.Context, pool poolManagerTypes.PoolI, keepers *keepers.AppKeepers) (bool, uint64, string, sdk.Dec, error) {
// We only want to upgrade balancer pools.
if pool.GetType() != poolManagerTypes.Balancer {
return true, 0, "", sdk.Dec{}, nil
}

gammPoolId := pool.GetId()

// Skip pools that are already linked.
clPoolId, err := keepers.GAMMKeeper.GetLinkedConcentratedPoolID(ctx, gammPoolId)
if err == nil && clPoolId != 0 {
ctx.Logger().Info(fmt.Sprintf("gammPoolId %d is already linked to CL pool %d, skipping", gammPoolId, clPoolId))
return true, 0, "", sdk.Dec{}, nil
}

cfmmPool, err := keepers.GAMMKeeper.GetCFMMPool(ctx, gammPoolId)
if err != nil {
return true, 0, "", sdk.Dec{}, err
}

if len(cfmmPool.GetTotalPoolLiquidity(ctx)) != 2 {
return true, 0, "", sdk.Dec{}, nil
}

poolCoins := cfmmPool.GetTotalPoolLiquidity(ctx)

// We only want to upgrade pools paired with OSMO. OSMO will be the quote asset.
quoteAsset, baseAsset := "", ""
for _, coin := range poolCoins {
if coin.Denom == QuoteAsset {
quoteAsset = coin.Denom
} else {
baseAsset = coin.Denom
}
}
if quoteAsset == "" || baseAsset == "" {
return true, 0, "", sdk.Dec{}, nil
}

// Set the spread factor to the same spread factor the GAMM pool was.
// If its spread factor is not authorized, set it to the first authorized non-zero spread factor.
spreadFactor := cfmmPool.GetSpreadFactor(ctx)
authorizedSpreadFactors := keepers.ConcentratedLiquidityKeeper.GetParams(ctx).AuthorizedSpreadFactors
spreadFactorAuthorized := false
for _, authorizedSpreadFactor := range authorizedSpreadFactors {
if authorizedSpreadFactor.Equal(spreadFactor) {
spreadFactorAuthorized = true
break
}
}
if !spreadFactorAuthorized {
spreadFactor = authorizedSpreadFactors[1]
}
return false, gammPoolId, baseAsset, spreadFactor, nil
}
Loading