diff --git a/protocol/lib/collections.go b/protocol/lib/collections.go index e6f4fcebf4..a1377eb758 100644 --- a/protocol/lib/collections.go +++ b/protocol/lib/collections.go @@ -152,3 +152,13 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { } return combinedMap } + +// Check if slice contains a particular value +func SliceContains[T comparable](list []T, value T) bool { + for _, v := range list { + if v == value { + return true + } + } + return false +} diff --git a/protocol/lib/collections_test.go b/protocol/lib/collections_test.go index 153489fc35..ced21c54c3 100644 --- a/protocol/lib/collections_test.go +++ b/protocol/lib/collections_test.go @@ -471,3 +471,15 @@ func TestMergeAllMapsWithDistinctKeys(t *testing.T) { }) } } + +func TestSliceContains(t *testing.T) { + require.True( + t, + lib.SliceContains([]uint32{1, 2}, 1), + ) + + require.False( + t, + lib.SliceContains([]uint32{1, 2}, 3), + ) +} diff --git a/protocol/x/accountplus/keeper/timestampnonce.go b/protocol/x/accountplus/keeper/timestampnonce.go index 8dc5070c31..e23a0196a6 100644 --- a/protocol/x/accountplus/keeper/timestampnonce.go +++ b/protocol/x/accountplus/keeper/timestampnonce.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" ) @@ -80,6 +81,11 @@ func AttemptTimestampNonceUpdate( return false } + // Must be unique + if lib.SliceContains(tsNonceDetails.TimestampNonces, tsNonce) { + return false + } + if len(tsNonceDetails.TimestampNonces) < MaxTimestampNonceArrSize { tsNonceDetails.TimestampNonces = append(tsNonceDetails.TimestampNonces, tsNonce) return true diff --git a/protocol/x/accountplus/keeper/timestampnonce_test.go b/protocol/x/accountplus/keeper/timestampnonce_test.go index 9292f92ae9..9d283b88a1 100644 --- a/protocol/x/accountplus/keeper/timestampnonce_test.go +++ b/protocol/x/accountplus/keeper/timestampnonce_test.go @@ -127,7 +127,7 @@ func TestEjectStaleTsNonces(t *testing.T) { func TestAttemptTimestampNonceUpdate(t *testing.T) { startTs := keeper.TimestampNonceSequenceCutoff - t.Run("Will not update if ts nonce <= maxEjectedNonce", func(t *testing.T) { + t.Run("Will reject ts nonce <= maxEjectedNonce", func(t *testing.T) { tsNonce := startTs + 10 var tsNonces []uint64 @@ -157,7 +157,37 @@ func TestAttemptTimestampNonceUpdate(t *testing.T) { require.Equal(t, expectedAccountState, accountState) }) - t.Run("Will update if ts nonces has capacity (ts nonce > maxEjectedNonce)", func(t *testing.T) { + t.Run("Will reject duplicate ts nonce", func(t *testing.T) { + tsNonce := startTs + 20 + + var tsNonces []uint64 + for i := range 5 { + tsNonces = append(tsNonces, startTs+uint64(i)+20) + } + + accountState := types.AccountState{ + Address: constants.AliceAccAddress.String(), + TimestampNonceDetails: types.TimestampNonceDetails{ + TimestampNonces: tsNonces, + MaxEjectedNonce: startTs + 10, + }, + } + + expectedAccountState := types.AccountState{ + Address: constants.AliceAccAddress.String(), + TimestampNonceDetails: types.TimestampNonceDetails{ + TimestampNonces: tsNonces, + MaxEjectedNonce: startTs + 10, + }, + } + + updated := keeper.AttemptTimestampNonceUpdate(tsNonce, &accountState) + + require.False(t, updated) + require.Equal(t, expectedAccountState, accountState) + }) + + t.Run("Will update if ts nonces has capacity (given ts unique and ts > maxEjectedNonce)", func(t *testing.T) { tsNonce := startTs + 11 var tsNonces []uint64 @@ -188,9 +218,9 @@ func TestAttemptTimestampNonceUpdate(t *testing.T) { }) t.Run( - "Will not update if ts nonce <= existing ts nonces (timestamp nonce > maxEjectedNonce)", + "Will not update if ts nonce <= existing ts nonces (given ts unique and ts > maxEjectedNonce)", func(t *testing.T) { - tsNonce := startTs + 20 + tsNonce := startTs + 19 var tsNonces []uint64 for i := range keeper.MaxTimestampNonceArrSize { @@ -220,15 +250,15 @@ func TestAttemptTimestampNonceUpdate(t *testing.T) { }) t.Run( - "Will update if ts nonce larger than at least one existing ts nonce (timestamp nonce > maxEjectedNonce)", + "Will update if ts nonce larger than at least one existing ts nonce (given ts unique and ts > maxEjectedNonce)", func(t *testing.T) { - tsNonce := startTs + 21 - var tsNonces []uint64 for i := range keeper.MaxTimestampNonceArrSize { tsNonces = append(tsNonces, startTs+uint64(i)+20) } + tsNonce := tsNonces[len(tsNonces)-1] + 1 + accountState := types.AccountState{ Address: constants.AliceAccAddress.String(), TimestampNonceDetails: types.TimestampNonceDetails{