Skip to content

Commit

Permalink
Fix: deterministically fetch perp info from state (backport #2341) (#…
Browse files Browse the repository at this point in the history
…2346)

Co-authored-by: ttl33 <[email protected]>
  • Loading branch information
mergify[bot] and ttl33 authored Sep 25, 2024
1 parent c34d56d commit c4237d1
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 8 deletions.
7 changes: 7 additions & 0 deletions protocol/testutil/constants/positions.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ var (
big.NewInt(0),
big.NewInt(0),
)
// SOL positions
PerpetualPosition_OneSolLong = *testutil.CreateSinglePerpetualPosition(
2,
big.NewInt(100_000_000_000), // 1 SOL
big.NewInt(0),
big.NewInt(0),
)
// Long position for arbitrary isolated market
PerpetualPosition_OneISOLong = *testutil.CreateSinglePerpetualPosition(
3,
Expand Down
19 changes: 11 additions & 8 deletions protocol/x/subaccounts/keeper/subaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,30 +770,33 @@ func (k Keeper) GetAllRelevantPerpetuals(
perptypes.PerpInfos,
error,
) {
subaccountIds := make(map[types.SubaccountId]struct{})
perpIds := make(map[uint32]struct{})
subaccountIdsSet := make(map[types.SubaccountId]struct{})
perpIdsSet := make(map[uint32]struct{})

// Add all relevant perpetuals in every update.
for _, update := range updates {
// If this subaccount has not been processed already, get all of its existing perpetuals.
if _, exists := subaccountIds[update.SubaccountId]; !exists {
if _, exists := subaccountIdsSet[update.SubaccountId]; !exists {
sa := k.GetSubaccount(ctx, update.SubaccountId)
for _, postition := range sa.PerpetualPositions {
perpIds[postition.PerpetualId] = struct{}{}
perpIdsSet[postition.PerpetualId] = struct{}{}
}
subaccountIds[update.SubaccountId] = struct{}{}
subaccountIdsSet[update.SubaccountId] = struct{}{}
}

// Add all perpetuals in the update.
for _, perpUpdate := range update.PerpetualUpdates {
perpIds[perpUpdate.GetId()] = struct{}{}
perpIdsSet[perpUpdate.GetId()] = struct{}{}
}
}

// Important: Sort the perpIds to ensure determinism!
sortedPerpIds := lib.GetSortedKeys[lib.Sortable[uint32]](perpIdsSet)

// Get all perpetual information from state.
ltCache := make(map[uint32]perptypes.LiquidityTier)
perpInfos := make(perptypes.PerpInfos, len(perpIds))
for perpId := range perpIds {
perpInfos := make(perptypes.PerpInfos, len(sortedPerpIds))
for _, perpId := range sortedPerpIds {
perpetual, price, err := k.perpetualsKeeper.GetPerpetualAndMarketPrice(ctx, perpId)
if err != nil {
return nil, err
Expand Down
112 changes: 112 additions & 0 deletions protocol/x/subaccounts/keeper/subaccount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/dydxprotocol/v4-chain/protocol/lib"

storetypes "cosmossdk.io/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/dydxprotocol/v4-chain/protocol/dtypes"
Expand Down Expand Up @@ -6019,3 +6020,114 @@ func TestGetNetCollateralAndMarginRequirements(t *testing.T) {
})
}
}

func TestGetAllRelevantPerpetuals_Deterministic(t *testing.T) {
tests := map[string]struct {
// state
perpetuals []perptypes.Perpetual

// subaccount state
assetPositions []*types.AssetPosition
perpetualPositions []*types.PerpetualPosition

// updates
assetUpdates []types.AssetUpdate
perpetualUpdates []types.PerpetualUpdate
}{
"Gas used is deterministic when erroring on gas usage": {
assetPositions: testutil.CreateUsdcAssetPositions(big.NewInt(10_000_000_001)), // $10,000.000001
perpetuals: []perptypes.Perpetual{
constants.BtcUsd_NoMarginRequirement,
constants.EthUsd_NoMarginRequirement,
constants.SolUsd_20PercentInitial_10PercentMaintenance,
},
perpetualPositions: []*types.PerpetualPosition{
&constants.PerpetualPosition_OneBTCLong,
&constants.PerpetualPosition_OneTenthEthLong,
&constants.PerpetualPosition_OneSolLong,
},
assetUpdates: []types.AssetUpdate{
{
AssetId: constants.Usdc.Id,
BigQuantumsDelta: big.NewInt(1_000_000), // +1 USDC
},
},
perpetualUpdates: []types.PerpetualUpdate{
{
PerpetualId: uint32(0),
BigQuantumsDelta: big.NewInt(-200_000_000), // -2 BTC
},
{
PerpetualId: uint32(1),
BigQuantumsDelta: big.NewInt(250_000_000), // .25 ETH
},
{
PerpetualId: uint32(2),
BigQuantumsDelta: big.NewInt(500_000_000), // .005 SOL
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// Setup.
ctx, keeper, pricesKeeper, perpetualsKeeper, _, _, assetsKeeper, _, _, _, _ := keepertest.SubaccountsKeepers(
t,
true,
)
keepertest.CreateTestMarkets(t, ctx, pricesKeeper)
keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper)
keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper)
for _, p := range tc.perpetuals {
perpetualsKeeper.SetPerpetualForTest(ctx, p)
}
require.NoError(t, keepertest.CreateUsdcAsset(ctx, assetsKeeper))

subaccount := createNSubaccount(keeper, ctx, 1, big.NewInt(1_000))[0]
subaccount.PerpetualPositions = tc.perpetualPositions
subaccount.AssetPositions = tc.assetPositions
keeper.SetSubaccount(ctx, subaccount)
subaccountId := *subaccount.Id

update := types.Update{
SubaccountId: subaccountId,
AssetUpdates: tc.assetUpdates,
PerpetualUpdates: tc.perpetualUpdates,
}

// Execute.
gasUsedBefore := ctx.GasMeter().GasConsumed()
_, err := keeper.GetAllRelevantPerpetuals(ctx, []types.Update{update})
require.NoError(t, err)
gasUsedAfter := ctx.GasMeter().GasConsumed()

gasUsed := uint64(0)
// Run 100 times since it's highly unlikely gas usage is deterministic over 100 times if
// there's non-determinism.
for range 100 {
// divide by 2 so that the state read fails at least second to last time.
ctxWithLimitedGas := ctx.WithGasMeter(storetypes.NewGasMeter((gasUsedAfter - gasUsedBefore) / 2))

require.PanicsWithValue(
t,
storetypes.ErrorOutOfGas{Descriptor: "ReadFlat"},
func() {
_, _ = keeper.GetAllRelevantPerpetuals(ctxWithLimitedGas, []types.Update{update})
},
)

if gasUsed == 0 {
gasUsed = ctxWithLimitedGas.GasMeter().GasConsumed()
require.Greater(t, gasUsed, uint64(0))
} else {
require.Equal(
t,
gasUsed,
ctxWithLimitedGas.GasMeter().GasConsumed(),
"Gas usage when out of gas is not deterministic",
)
}
}
})
}
}

0 comments on commit c4237d1

Please sign in to comment.