Skip to content

Commit

Permalink
Fix: deterministically fetch perp info from state (#2341)
Browse files Browse the repository at this point in the history
(cherry picked from commit cc1b795)

# Conflicts:
#	protocol/testutil/constants/positions.go
#	protocol/x/subaccounts/keeper/subaccount.go
#	protocol/x/subaccounts/keeper/subaccount_test.go
  • Loading branch information
ttl33 authored and mergify[bot] committed Sep 25, 2024
1 parent 8843936 commit 1d12b92
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 6 deletions.
100 changes: 100 additions & 0 deletions protocol/testutil/constants/positions.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

var (
// Perpetual Positions.
<<<<<<< HEAD
Long_Perp_1BTC_PositiveFunding = satypes.PerpetualPosition{
PerpetualId: 0,
Quantums: dtypes.NewInt(100_000_000), // 1 BTC
Expand Down Expand Up @@ -73,6 +74,105 @@ var (
PerpetualId: 1,
Quantums: dtypes.NewIntFromBigInt(BigNegMaxUint64()), // 18,446,744,070 ETH, -$55,340,232,210,000 notional.
}
=======
Long_Perp_1BTC_PositiveFunding = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(100_000_000), // 1 BTC
big.NewInt(0),
big.NewInt(0),
)
Short_Perp_1ETH_NegativeFunding = *testutil.CreateSinglePerpetualPosition(
1,
big.NewInt(-100_000_000), // 1 ETH
big.NewInt(-1),
big.NewInt(0),
)
PerpetualPosition_OneBTCLong = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(100_000_000), // 1 BTC, $50,000 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneBTCShort = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(-100_000_000), // 1 BTC, -$50,000 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneTenthBTCLong = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(10_000_000), // 0.1 BTC, $5,000 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneTenthBTCShort = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(-10_000_000), // 0.1 BTC, -$5,000 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneHundredthBTCLong = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(1_000_000), // 0.01 BTC, $500 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneHundredthBTCShort = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(-1_000_000), // 0.01 BTC, -$500 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_FourThousandthsBTCLong = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(400_000), // 0.004 BTC, $200 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_FourThousandthsBTCShort = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(-400_000), // 0.004 BTC, -$200 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneAndHalfBTCLong = *testutil.CreateSinglePerpetualPosition(
0,
big.NewInt(150_000_000), // 1.5 BTC, $75,000 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneTenthEthLong = *testutil.CreateSinglePerpetualPosition(
1,
big.NewInt(100_000_000), // 0.1 ETH, $300 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_OneTenthEthShort = *testutil.CreateSinglePerpetualPosition(
1,
big.NewInt(-100_000_000), // 0.1 ETH, -$300 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_MaxUint64EthLong = *testutil.CreateSinglePerpetualPosition(
1,
big.NewInt(0).SetUint64(math.MaxUint64), // 18,446,744,070 ETH, $55,340,232,210,000 notional.
big.NewInt(0),
big.NewInt(0),
)
PerpetualPosition_MaxUint64EthShort = *testutil.CreateSinglePerpetualPosition(
1,
BigNegMaxUint64(), // 18,446,744,070 ETH, -$55,340,232,210,000 notional.
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),
)
>>>>>>> cc1b7954 (Fix: deterministically fetch perp info from state (#2341))
// Long position for arbitrary isolated market
PerpetualPosition_OneISOLong = satypes.PerpetualPosition{
PerpetualId: 3,
Expand Down
22 changes: 16 additions & 6 deletions protocol/x/subaccounts/keeper/subaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -1027,33 +1027,43 @@ func (k Keeper) GetAllRelevantPerpetuals(
map[uint32]perptypes.PerpInfo,
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.
<<<<<<< HEAD
perpetuals := make(map[uint32]perptypes.PerpInfo, len(perpIds))
for perpId := range perpIds {
perpetual,
price,
liquidityTier,
err := k.perpetualsKeeper.GetPerpetualAndMarketPriceAndLiquidityTier(ctx, perpId)
=======
ltCache := make(map[uint32]perptypes.LiquidityTier)
perpInfos := make(perptypes.PerpInfos, len(sortedPerpIds))
for _, perpId := range sortedPerpIds {
perpetual, price, err := k.perpetualsKeeper.GetPerpetualAndMarketPrice(ctx, perpId)
>>>>>>> cc1b7954 (Fix: deterministically fetch perp info from state (#2341))
if err != nil {
return nil, err
}
Expand Down
111 changes: 111 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 @@ -5850,6 +5851,7 @@ func TestGetNetCollateralAndMarginRequirements(t *testing.T) {
}
}

<<<<<<< HEAD
func TestIsValidStateTransitionForUndercollateralizedSubaccount_ZeroMarginRequirements(t *testing.T) {
tests := map[string]struct {
bigCurNetCollateral *big.Int
Expand Down Expand Up @@ -5934,6 +5936,115 @@ func TestIsValidStateTransitionForUndercollateralizedSubaccount_ZeroMarginRequir
tc.bigNewMaintenanceMargin,
),
)
=======
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",
)
}
}
>>>>>>> cc1b7954 (Fix: deterministically fetch perp info from state (#2341))
})
}
}

0 comments on commit 1d12b92

Please sign in to comment.