diff --git a/Makefile b/Makefile index 0f1475398d..48b4cc8e82 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,7 @@ endif ldflags += $(LDFLAGS) ldflags := $(strip $(ldflags)) +# BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' -race BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' #### Command List #### diff --git a/loadtest/contracts/deploy_ten_contracts.sh b/loadtest/contracts/deploy_ten_contracts.sh index 9b325946ef..567b29252f 100644 --- a/loadtest/contracts/deploy_ten_contracts.sh +++ b/loadtest/contracts/deploy_ten_contracts.sh @@ -143,6 +143,8 @@ marsproposalid4=$(python3 parser.py proposal_id $marspair4) printf "12345678\n" | $seidbin tx gov deposit $marsproposalid4 10000000usei -y --from=$keyname --chain-id=$chainid --fees=10000000usei --gas=500000 --broadcast-mode=block printf "12345678\n" | $seidbin tx gov vote $marsproposalid4 yes -y --from=$keyname --chain-id=$chainid --fees=10000000usei --gas=500000 --broadcast-mode=block +sleep 90 + printf "12345678\n" | $seidbin tx staking unbond $valaddr 1000000000usei --from=$keyname --chain-id=$chainid -b block -y echo $marsaddr diff --git a/scripts/old_initialize_local.sh b/scripts/old_initialize_local.sh new file mode 100644 index 0000000000..085bbcb9a1 --- /dev/null +++ b/scripts/old_initialize_local.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +echo -n OS Password: +read -s password +echo +echo -n Key Name: +read keyname +echo +echo -n Number of Test Accounts: +read numtestaccount +echo + +docker stop jaeger +docker rm jaeger +docker run -d --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 5775:5775/udp \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.33 + +echo "Building..." +make install +echo $password | sudo -S rm -r ~/.sei/ +echo $password | sudo -S rm -r ~/test_accounts/ +~/go/bin/seid tendermint unsafe-reset-all +~/go/bin/seid init demo --chain-id sei-chain +yes | ~/go/bin/seid keys add $keyname +yes | ~/go/bin/seid keys add faucet +printf '12345678\n' | ~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show $keyname -a) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom +printf '12345678\n' | ~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show faucet -a) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom +python3 ./loadtest/scripts/populate_genesis_accounts.py $numtestaccount loc +printf '12345678\n' | ~/go/bin/seid gentx $keyname 70000000000000000000usei --chain-id sei-chain +~/go/bin/seid collect-gentxs +cat ~/.sei/config/genesis.json | jq '.app_state["crisis"]["constant_fee"]["denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["mint"]["params"]["mint_denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["staking"]["params"]["bond_denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["max_deposit_period"]="300s"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["gov"]["voting_params"]["voting_period"]="5s"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.consensus_params["block"]["time_iota_ms"]="50"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json + +# set block time to 2s +if [ ! -z "$1" ]; then + CONFIG_PATH="$1" +else + CONFIG_PATH="$HOME/.sei/config/config.toml" +fi + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + sed -i 's/timeout_prevote =.*/timeout_prevote = "2000ms"/g' $CONFIG_PATH + sed -i 's/timeout_precommit =.*/timeout_precommit = "2000ms"/g' $CONFIG_PATH + sed -i 's/timeout_commit =.*/timeout_commit = "2000ms"/g' $CONFIG_PATH + sed -i 's/skip_timeout_commit =.*/skip_timeout_commit = false/g' $CONFIG_PATH +elif [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' 's/timeout_prevote =.*/timeout_prevote = "2000ms"/g' $CONFIG_PATH + sed -i '' 's/timeout_precommit =.*/timeout_precommit = "2000ms"/g' $CONFIG_PATH + sed -i '' 's/timeout_commit =.*/timeout_commit = "2000ms"/g' $CONFIG_PATH + sed -i '' 's/skip_timeout_commit =.*/skip_timeout_commit = false/g' $CONFIG_PATH +else + printf "Platform not supported, please ensure that the following values are set in your config.toml:\n" + printf "### Consensus Configuration Options ###\n" + printf "\t timeout_prevote = \"2000ms\"\n" + printf "\t timeout_precommit = \"2000ms\"\n" + printf "\t timeout_commit = \"2000ms\"\n" + printf "\t skip_timeout_commit = false\n" + exit 1 +fi + +# start the chain with log tracing +GORACE="log_path=/tmp/race/seid_race" ~/go/bin/seid start --trace \ No newline at end of file diff --git a/sync/gas.go b/sync/gas.go new file mode 100644 index 0000000000..e13a1307d1 --- /dev/null +++ b/sync/gas.go @@ -0,0 +1,30 @@ +package sync + +import ( + "sync" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type GasWrapper struct { + sdk.GasMeter + mu *sync.Mutex +} + +func NewGasWrapper(wrapped sdk.GasMeter) sdk.GasMeter { + return GasWrapper{GasMeter: wrapped, mu: &sync.Mutex{}} +} + +func (g GasWrapper) ConsumeGas(amount sdk.Gas, descriptor string) { + g.mu.Lock() + defer g.mu.Unlock() + + g.GasMeter.ConsumeGas(amount, descriptor) +} + +func (g GasWrapper) RefundGas(amount sdk.Gas, descriptor string) { + g.mu.Lock() + defer g.mu.Unlock() + + g.GasMeter.RefundGas(amount, descriptor) +} diff --git a/utils/panic.go b/utils/panic.go index b711290180..e615de852a 100644 --- a/utils/panic.go +++ b/utils/panic.go @@ -17,7 +17,7 @@ func PanicHandler(recoverCallback func(any)) func() { } func MetricsPanicCallback(err any, ctx sdk.Context, key string) { - ctx.Logger().Error(fmt.Sprintf("panic occurred during order matching for: %s", key)) + ctx.Logger().Error(fmt.Sprintf("panic %s occurred during order matching for: %s", err, key)) telemetry.IncrCounterWithLabels( []string{key}, 1, diff --git a/x/dex/cache/cache.go b/x/dex/cache/cache.go index 0bb318d034..e7a1dac79e 100644 --- a/x/dex/cache/cache.go +++ b/x/dex/cache/cache.go @@ -2,6 +2,7 @@ package dex import ( "fmt" + "sync" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -19,21 +20,29 @@ type memStateItem interface { type memStateItems[T memStateItem] struct { internal []T copier func(T) T + + mu *sync.Mutex } func NewItems[T memStateItem](copier func(T) T) memStateItems[T] { - return memStateItems[T]{internal: []T{}, copier: copier} + return memStateItems[T]{internal: []T{}, copier: copier, mu: &sync.Mutex{}} } func (i *memStateItems[T]) Get() []T { + i.mu.Lock() + defer i.mu.Unlock() return i.internal } func (i *memStateItems[T]) Add(newItem T) { + i.mu.Lock() + defer i.mu.Unlock() i.internal = append(i.internal, newItem) } func (i *memStateItems[T]) FilterByAccount(account string) { + i.mu.Lock() + defer i.mu.Unlock() newItems := []T{} for _, item := range i.internal { if item.GetAccount() == account { @@ -45,6 +54,8 @@ func (i *memStateItems[T]) FilterByAccount(account string) { } func (i *memStateItems[T]) Copy() *memStateItems[T] { + i.mu.Lock() + defer i.mu.Unlock() copy := NewItems(i.copier) for _, item := range i.internal { copy.Add(i.copier(item)) diff --git a/x/dex/cache/cancel.go b/x/dex/cache/cancel.go index aa30ea9e5f..7c65d27fcd 100644 --- a/x/dex/cache/cancel.go +++ b/x/dex/cache/cancel.go @@ -19,6 +19,9 @@ func (o *BlockCancellations) Copy() *BlockCancellations { } func (o *BlockCancellations) FilterByIds(idsToRemove []uint64) { + o.mu.Lock() + defer o.mu.Unlock() + newItems := []*types.Cancellation{} badIDSet := datastructures.NewSyncSet(idsToRemove) for _, cancel := range o.internal { @@ -30,6 +33,9 @@ func (o *BlockCancellations) FilterByIds(idsToRemove []uint64) { } func (o *BlockCancellations) GetIdsToCancel() []uint64 { + o.mu.Lock() + defer o.mu.Unlock() + res := []uint64{} for _, cancel := range o.internal { res = append(res, cancel.Id) diff --git a/x/dex/cache/liquidation.go b/x/dex/cache/liquidation.go index 6e3c6650a8..770eefdb57 100644 --- a/x/dex/cache/liquidation.go +++ b/x/dex/cache/liquidation.go @@ -24,6 +24,9 @@ func (lrs *LiquidationRequests) Copy() *LiquidationRequests { } func (lrs *LiquidationRequests) IsAccountLiquidating(accountToLiquidate string) bool { + lrs.mu.Lock() + defer lrs.mu.Unlock() + for _, lr := range lrs.internal { if lr.AccountToLiquidate == accountToLiquidate { return true diff --git a/x/dex/cache/order.go b/x/dex/cache/order.go index 032325dc26..e82905f7a2 100644 --- a/x/dex/cache/order.go +++ b/x/dex/cache/order.go @@ -22,6 +22,8 @@ func (o *BlockOrders) Copy() *BlockOrders { } func (o *BlockOrders) MarkFailedToPlaceByAccounts(accounts []string) { + o.mu.Lock() + defer o.mu.Unlock() badAccountSet := datastructures.NewSyncSet(accounts) newOrders := []*types.Order{} for _, order := range o.internal { @@ -35,6 +37,8 @@ func (o *BlockOrders) MarkFailedToPlaceByAccounts(accounts []string) { } func (o *BlockOrders) MarkFailedToPlace(failedOrders []wasm.UnsuccessfulOrder) { + o.mu.Lock() + defer o.mu.Unlock() failedOrdersMap := map[uint64]wasm.UnsuccessfulOrder{} for _, failedOrder := range failedOrders { failedOrdersMap[failedOrder.ID] = failedOrder @@ -51,6 +55,9 @@ func (o *BlockOrders) MarkFailedToPlace(failedOrders []wasm.UnsuccessfulOrder) { } func (o *BlockOrders) GetSortedMarketOrders(direction types.PositionDirection, includeLiquidationOrders bool) []*types.Order { + o.mu.Lock() + defer o.mu.Unlock() + res := o.getOrdersByCriteria(types.OrderType_MARKET, direction) if includeLiquidationOrders { res = append(res, o.getOrdersByCriteria(types.OrderType_LIQUIDATION, direction)...) @@ -76,6 +83,8 @@ func (o *BlockOrders) GetSortedMarketOrders(direction types.PositionDirection, i } func (o *BlockOrders) GetLimitOrders(direction types.PositionDirection) []*types.Order { + o.mu.Lock() + defer o.mu.Unlock() return o.getOrdersByCriteria(types.OrderType_LIMIT, direction) } diff --git a/x/dex/contract/abci.go b/x/dex/contract/abci.go index b3826ebdd9..61ddb3851b 100644 --- a/x/dex/contract/abci.go +++ b/x/dex/contract/abci.go @@ -9,6 +9,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/store/whitelist/multi" + seisync "github.com/sei-protocol/sei-chain/sync" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/utils/datastructures" dexcache "github.com/sei-protocol/sei-chain/x/dex/cache" @@ -16,6 +17,7 @@ import ( dexkeeperabci "github.com/sei-protocol/sei-chain/x/dex/keeper/abci" dexkeeperutils "github.com/sei-protocol/sei-chain/x/dex/keeper/utils" "github.com/sei-protocol/sei-chain/x/dex/types" + dextypesutils "github.com/sei-protocol/sei-chain/x/dex/types/utils" dextypeswasm "github.com/sei-protocol/sei-chain/x/dex/types/wasm" "github.com/sei-protocol/sei-chain/x/store" otrace "go.opentelemetry.io/otel/trace" @@ -27,15 +29,18 @@ type environment struct { finalizeBlockMessages *datastructures.TypedSyncMap[string, *dextypeswasm.SudoFinalizeBlockMsg] settlementsByContract *datastructures.TypedSyncMap[string, []*types.SettlementEntry] executionTerminationSignals *datastructures.TypedSyncMap[string, chan struct{}] + registeredPairs *datastructures.TypedSyncMap[string, []types.Pair] + orderBooks *datastructures.TypedNestedSyncMap[string, dextypesutils.PairString, *types.OrderBook] - finalizeMsgMutex *sync.Mutex + finalizeMsgMutex *sync.Mutex + eventManagerMutex *sync.Mutex } func EndBlockerAtomic(ctx sdk.Context, keeper *keeper.Keeper, validContractsInfo []types.ContractInfo, tracingInfo *tracing.Info) ([]types.ContractInfo, bool) { tracer := tracingInfo.Tracer spanCtx, span := (*tracer).Start(tracingInfo.TracerContext, "DexEndBlockerAtomic") defer span.End() - env := newEnv(validContractsInfo) + env := newEnv(ctx, validContractsInfo, keeper) ctx, msCached := cacheAndDecorateContext(ctx, env) memStateCopy := keeper.MemState.DeepCopy() @@ -47,6 +52,7 @@ func EndBlockerAtomic(ctx sdk.Context, keeper *keeper.Keeper, validContractsInfo runner.Run() handleSettlements(spanCtx, ctx, env, keeper, tracer) + handleUnfulfilledMarketOrders(spanCtx, ctx, env, keeper, tracer) handleFinalizedBlocks(spanCtx, ctx, env, keeper, tracer) // No error is thrown for any contract. This should happen most of the time. @@ -60,14 +66,24 @@ func EndBlockerAtomic(ctx sdk.Context, keeper *keeper.Keeper, validContractsInfo return filterNewValidContracts(env, keeper), false } -func newEnv(validContractsInfo []types.ContractInfo) *environment { +func newEnv(ctx sdk.Context, validContractsInfo []types.ContractInfo, keeper *keeper.Keeper) *environment { finalizeBlockMessages := datastructures.NewTypedSyncMap[string, *dextypeswasm.SudoFinalizeBlockMsg]() settlementsByContract := datastructures.NewTypedSyncMap[string, []*types.SettlementEntry]() executionTerminationSignals := datastructures.NewTypedSyncMap[string, chan struct{}]() + registeredPairs := datastructures.NewTypedSyncMap[string, []types.Pair]() + orderBooks := datastructures.NewTypedNestedSyncMap[string, dextypesutils.PairString, *types.OrderBook]() for _, contract := range validContractsInfo { finalizeBlockMessages.Store(contract.ContractAddr, dextypeswasm.NewSudoFinalizeBlockMsg()) settlementsByContract.Store(contract.ContractAddr, []*types.SettlementEntry{}) executionTerminationSignals.Store(contract.ContractAddr, make(chan struct{}, 1)) + contractPairs := keeper.GetAllRegisteredPairs(ctx, contract.ContractAddr) + registeredPairs.Store(contract.ContractAddr, contractPairs) + for _, pair := range contractPairs { + pair := pair + orderBooks.StoreNested(contract.ContractAddr, dextypesutils.GetPairString(&pair), dexkeeperutils.PopulateOrderbook( + ctx, keeper, dextypesutils.ContractAddress(contract.ContractAddr), pair, + )) + } } return &environment{ validContractsInfo: validContractsInfo, @@ -75,7 +91,10 @@ func newEnv(validContractsInfo []types.ContractInfo) *environment { finalizeBlockMessages: finalizeBlockMessages, settlementsByContract: settlementsByContract, executionTerminationSignals: executionTerminationSignals, + registeredPairs: registeredPairs, + orderBooks: orderBooks, finalizeMsgMutex: &sync.Mutex{}, + eventManagerMutex: &sync.Mutex{}, } } @@ -83,12 +102,17 @@ func cacheAndDecorateContext(ctx sdk.Context, env *environment) (sdk.Context, sd cachedCtx, msCached := store.GetCachedContext(ctx) goCtx := context.WithValue(cachedCtx.Context(), dexcache.CtxKeyExecTermSignal, env.executionTerminationSignals) cachedCtx = cachedCtx.WithContext(goCtx) - return cachedCtx, msCached + decoratedCtx := cachedCtx.WithGasMeter(seisync.NewGasWrapper(cachedCtx.GasMeter())).WithBlockGasMeter( + seisync.NewGasWrapper(cachedCtx.BlockGasMeter()), + ) + return decoratedCtx, msCached } func decorateContextForContract(ctx sdk.Context, contractInfo types.ContractInfo) sdk.Context { goCtx := context.WithValue(ctx.Context(), dexcache.CtxKeyExecutingContract, contractInfo) - return ctx.WithContext(goCtx).WithMultiStore(multi.NewStore(ctx.MultiStore(), GetWhitelistMap(contractInfo.ContractAddr))) + whitelistedStore := multi.NewStore(ctx.MultiStore(), GetWhitelistMap(contractInfo.ContractAddr)) + newEventManager := sdk.NewEventManager() + return ctx.WithContext(goCtx).WithMultiStore(whitelistedStore).WithEventManager(newEventManager) } func handleDeposits(ctx sdk.Context, env *environment, keeper *keeper.Keeper, tracer *otrace.Tracer) { @@ -122,6 +146,22 @@ func handleSettlements(ctx context.Context, sdkCtx sdk.Context, env *environment }) } +func handleUnfulfilledMarketOrders(ctx context.Context, sdkCtx sdk.Context, env *environment, keeper *keeper.Keeper, tracer *otrace.Tracer) { + // Cancel unfilled market orders + for _, contract := range env.validContractsInfo { + if contract.NeedOrderMatching { + registeredPairs, found := env.registeredPairs.Load(contract.ContractAddr) + if !found { + continue + } + if err := CancelUnfulfilledMarketOrders(ctx, sdkCtx, contract.ContractAddr, keeper, registeredPairs, tracer); err != nil { + sdkCtx.Logger().Error(fmt.Sprintf("Error cancelling unfulfilled market orders for %s", contract.ContractAddr)) + env.failedContractAddresses.Add(contract.ContractAddr) + } + } + } +} + func handleFinalizedBlocks(ctx context.Context, sdkCtx sdk.Context, env *environment, keeper *keeper.Keeper, tracer *otrace.Tracer) { _, span := (*tracer).Start(ctx, "DexEndBlockerHandleFinalizedBlocks") defer span.End() @@ -154,22 +194,34 @@ func orderMatchingRunnable(ctx context.Context, sdkContext sdk.Context, env *env if !contractInfo.NeedOrderMatching { return } + parentSdkContext := sdkContext sdkContext = decorateContextForContract(sdkContext, contractInfo) sdkContext.Logger().Info(fmt.Sprintf("End block for %s", contractInfo.ContractAddr)) - if orderResultsMap, settlements, err := HandleExecutionForContract(ctx, sdkContext, contractInfo, keeper, tracer); err != nil { + pairs, pairFound := env.registeredPairs.Load(contractInfo.ContractAddr) + orderBooks, found := env.orderBooks.Load(contractInfo.ContractAddr) + if !pairFound || !found { + sdkContext.Logger().Error(fmt.Sprintf("No pair or order book for %s", contractInfo.ContractAddr)) + env.failedContractAddresses.Add(contractInfo.ContractAddr) + } else if orderResultsMap, settlements, err := HandleExecutionForContract(ctx, sdkContext, contractInfo, keeper, pairs, orderBooks, tracer); err != nil { sdkContext.Logger().Error(fmt.Sprintf("Error for EndBlock of %s", contractInfo.ContractAddr)) env.failedContractAddresses.Add(contractInfo.ContractAddr) } else { for account, orderResults := range orderResultsMap { // only add to finalize message for contract addresses if msg, ok := env.finalizeBlockMessages.Load(account); ok { - env.finalizeMsgMutex.Lock() - msg.AddContractResult(orderResults) - env.finalizeMsgMutex.Unlock() + // ordering of `AddContractResult` among multiple orderMatchingRunnable instances doesn't matter + // since it's not persisted as state, and it's only used for invoking registered contracts' + // FinalizeBlock sudo endpoints, whose state updates are gated by whitelist stores anyway. + msg.AddContractResult(orderResults, env.finalizeMsgMutex) } } env.settlementsByContract.Store(contractInfo.ContractAddr, settlements) } + + // ordering of events doesn't matter since events aren't part of consensus + env.eventManagerMutex.Lock() + defer env.eventManagerMutex.Unlock() + parentSdkContext.EventManager().EmitEvents(sdkContext.EventManager().Events()) } func orderMatchingRecoverCallback(err any, ctx sdk.Context, env *environment, contractInfo types.ContractInfo) { diff --git a/x/dex/contract/execution.go b/x/dex/contract/execution.go index 70d7f8c2ad..a8cf2e46ae 100644 --- a/x/dex/contract/execution.go +++ b/x/dex/contract/execution.go @@ -13,6 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/store/whitelist/multi" "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/datastructures" dexcache "github.com/sei-protocol/sei-chain/x/dex/cache" "github.com/sei-protocol/sei-chain/x/dex/exchange" "github.com/sei-protocol/sei-chain/x/dex/keeper" @@ -29,13 +30,13 @@ func CallPreExecutionHooks( sdkCtx sdk.Context, contractAddr string, dexkeeper *keeper.Keeper, + registeredPairs []types.Pair, tracer *otrace.Tracer, ) error { spanCtx, span := (*tracer).Start(ctx, "PreExecutionHooks") defer span.End() span.SetAttributes(attribute.String("contract", contractAddr)) abciWrapper := dexkeeperabci.KeeperWrapper{Keeper: dexkeeper} - registeredPairs := dexkeeper.GetAllRegisteredPairs(sdkCtx, contractAddr) if err := abciWrapper.HandleEBLiquidation(spanCtx, sdkCtx, tracer, contractAddr, registeredPairs); err != nil { return err } @@ -48,33 +49,15 @@ func CallPreExecutionHooks( return nil } -func CancelUnfulfilledMarketOrders( - ctx context.Context, - sdkCtx sdk.Context, - contractAddr string, - dexkeeper *keeper.Keeper, - tracer *otrace.Tracer, -) error { - spanCtx, span := (*tracer).Start(ctx, "CancelUnfulfilledMarketOrders") - span.SetAttributes(attribute.String("contract", contractAddr)) - defer span.End() - abciWrapper := dexkeeperabci.KeeperWrapper{Keeper: dexkeeper} - registeredPairs := dexkeeper.GetAllRegisteredPairs(sdkCtx, contractAddr) - if err := abciWrapper.HandleEBCancelOrders(spanCtx, sdkCtx, tracer, contractAddr, registeredPairs); err != nil { - return err - } - return nil -} - func ExecutePair( ctx sdk.Context, contractAddr string, pair types.Pair, dexkeeper *keeper.Keeper, + orderbook *types.OrderBook, ) []*types.SettlementEntry { typedContractAddr := dextypesutils.ContractAddress(contractAddr) typedPairStr := dextypesutils.GetPairString(&pair) - orderbook := dexkeeperutils.PopulateOrderbook(ctx, dexkeeper, typedContractAddr, pair) cancelForPair(ctx, typedContractAddr, typedPairStr, dexkeeper, orderbook) marketOrderOutcome := matchMarketOrderForPair(ctx, typedContractAddr, typedPairStr, dexkeeper, orderbook) @@ -147,7 +130,7 @@ func UpdateOrderState( typedContractAddr dextypesutils.ContractAddress, typedPairStr dextypesutils.PairString, dexkeeper *keeper.Keeper, - settlements []*types.SettlementEntry, + orderIDToSettledQuantities map[uint64]sdk.Dec, ) { orders := dexkeeper.MemState.GetBlockOrders(ctx, typedContractAddr, typedPairStr) cancels := dexkeeper.MemState.GetBlockCancels(ctx, typedContractAddr, typedPairStr) @@ -164,56 +147,28 @@ func UpdateOrderState( dexkeeper.UpdateOrderStatus(ctx, string(typedContractAddr), cancel.Id, types.OrderStatus_CANCELLED) } // Then deduct quantity from orders that have (partially) settled - for _, settlementEntry := range settlements { - // Market orders would have already had their quantities reduced during order matching - if settlementEntry.OrderType == dextypeswasm.GetContractOrderType(types.OrderType_LIMIT) { - dexkeeper.ReduceOrderQuantity(ctx, string(typedContractAddr), settlementEntry.OrderId, settlementEntry.Quantity) - } + for orderID, quantity := range orderIDToSettledQuantities { + dexkeeper.ReduceOrderQuantity(ctx, string(typedContractAddr), orderID, quantity) } // Finally update market order status based on execution result - for _, marketOrderID := range getUnfulfilledPlacedMarketOrderIds(ctx, typedContractAddr, typedPairStr, dexkeeper) { + for _, marketOrderID := range getUnfulfilledPlacedMarketOrderIds(ctx, typedContractAddr, typedPairStr, dexkeeper, orderIDToSettledQuantities) { dexkeeper.UpdateOrderStatus(ctx, string(typedContractAddr), marketOrderID, types.OrderStatus_CANCELLED) } } -func PrepareCancelUnfulfilledMarketOrders( - ctx sdk.Context, - typedContractAddr dextypesutils.ContractAddress, - typedPairStr dextypesutils.PairString, - dexkeeper *keeper.Keeper, -) { - dexkeeper.MemState.ClearCancellationForPair(ctx, typedContractAddr, typedPairStr) - for _, marketOrderID := range getUnfulfilledPlacedMarketOrderIds(ctx, typedContractAddr, typedPairStr, dexkeeper) { - dexkeeper.MemState.GetBlockCancels(ctx, typedContractAddr, typedPairStr).Add(&types.Cancellation{ - Id: marketOrderID, - Initiator: types.CancellationInitiator_USER, - }) - } -} - -func getUnfulfilledPlacedMarketOrderIds( - ctx sdk.Context, - typedContractAddr dextypesutils.ContractAddress, - typedPairStr dextypesutils.PairString, - dexkeeper *keeper.Keeper, -) []uint64 { - res := []uint64{} - for _, order := range dexkeeper.MemState.GetBlockOrders(ctx, typedContractAddr, typedPairStr).Get() { - if order.Status == types.OrderStatus_FAILED_TO_PLACE { - continue - } - if order.OrderType == types.OrderType_MARKET || order.OrderType == types.OrderType_LIQUIDATION { - if order.Quantity.IsPositive() { - res = append(res, order.Id) - } +func GetOrderIDToSettledQuantities(settlements []*types.SettlementEntry) map[uint64]sdk.Dec { + res := map[uint64]sdk.Dec{} + for _, settlement := range settlements { + if _, ok := res[settlement.OrderId]; !ok { + res[settlement.OrderId] = sdk.ZeroDec() } + res[settlement.OrderId] = res[settlement.OrderId].Add(settlement.Quantity) } return res } -func ExecutePairsInParallel(ctx sdk.Context, contractAddr string, dexkeeper *keeper.Keeper) ([]func(), []*types.SettlementEntry) { +func ExecutePairsInParallel(ctx sdk.Context, contractAddr string, dexkeeper *keeper.Keeper, registeredPairs []types.Pair, orderBooks *datastructures.TypedSyncMap[dextypesutils.PairString, *types.OrderBook]) ([]func(), []*types.SettlementEntry) { typedContractAddr := dextypesutils.ContractAddress(contractAddr) - registeredPairs := dexkeeper.GetAllRegisteredPairs(ctx, contractAddr) orderUpdaters := []func(){} settlements := []*types.SettlementEntry{} @@ -225,24 +180,34 @@ func ExecutePairsInParallel(ctx sdk.Context, contractAddr string, dexkeeper *kee wg.Add(1) pair := pair - pairCtx := ctx.WithMultiStore(multi.NewStore(ctx.MultiStore(), GetPerPairWhitelistMap(contractAddr, pair))) + pairCtx := ctx.WithMultiStore(multi.NewStore(ctx.MultiStore(), GetPerPairWhitelistMap(contractAddr, pair))).WithEventManager(sdk.NewEventManager()) go func() { defer wg.Done() defer utils.PanicHandler(func(err any) { + mu.Lock() + defer mu.Unlock() anyPanicked = true utils.MetricsPanicCallback(err, ctx, fmt.Sprintf("%s-%s|%s", contractAddr, pair.PriceDenom, pair.AssetDenom)) })() pairCopy := pair - pairSettlements := ExecutePair(pairCtx, contractAddr, pair, dexkeeper) - PrepareCancelUnfulfilledMarketOrders(pairCtx, typedContractAddr, dextypesutils.GetPairString(&pairCopy), dexkeeper) + pairStr := dextypesutils.GetPairString(&pairCopy) + orderbook, found := orderBooks.Load(pairStr) + if !found { + panic(fmt.Sprintf("Orderbook not found for %s", pairStr)) + } + pairSettlements := ExecutePair(pairCtx, contractAddr, pair, dexkeeper, orderbook.DeepCopy()) + orderIDToSettledQuantities := GetOrderIDToSettledQuantities(pairSettlements) + PrepareCancelUnfulfilledMarketOrders(pairCtx, typedContractAddr, pairStr, dexkeeper, orderIDToSettledQuantities) mu.Lock() defer mu.Unlock() orderUpdaters = append(orderUpdaters, func() { - UpdateOrderState(ctx, typedContractAddr, dextypesutils.GetPairString(&pairCopy), dexkeeper, pairSettlements) + UpdateOrderState(ctx, typedContractAddr, dextypesutils.GetPairString(&pairCopy), dexkeeper, orderIDToSettledQuantities) }) settlements = append(settlements, pairSettlements...) + // ordering of events doesn't matter since events aren't part of consensus + ctx.EventManager().EmitEvents(pairCtx.EventManager().Events()) }() } wg.Wait() @@ -259,6 +224,8 @@ func HandleExecutionForContract( sdkCtx sdk.Context, contract types.ContractInfo, dexkeeper *keeper.Keeper, + registeredPairs []types.Pair, + orderBooks *datastructures.TypedSyncMap[dextypesutils.PairString, *types.OrderBook], tracer *otrace.Tracer, ) (map[string]dextypeswasm.ContractOrderResult, []*types.SettlementEntry, error) { executionStart := time.Now() @@ -268,19 +235,15 @@ func HandleExecutionForContract( orderResults := map[string]dextypeswasm.ContractOrderResult{} // Call contract hooks so that contracts can do internal bookkeeping - if err := CallPreExecutionHooks(ctx, sdkCtx, contractAddr, dexkeeper, tracer); err != nil { + if err := CallPreExecutionHooks(ctx, sdkCtx, contractAddr, dexkeeper, registeredPairs, tracer); err != nil { return orderResults, []*types.SettlementEntry{}, err } - orderUpdaters, settlements := ExecutePairsInParallel(sdkCtx, contractAddr, dexkeeper) + orderUpdaters, settlements := ExecutePairsInParallel(sdkCtx, contractAddr, dexkeeper, registeredPairs, orderBooks) for _, orderUpdater := range orderUpdaters { orderUpdater() } - // Cancel unfilled market orders - if err := CancelUnfulfilledMarketOrders(ctx, sdkCtx, contractAddr, dexkeeper, tracer); err != nil { - return orderResults, settlements, err - } // populate order placement results for FinalizeBlock hook dexkeeper.MemState.GetAllBlockOrders(sdkCtx, typedContractAddr).DeepApply(func(orders *dexcache.BlockOrders) { diff --git a/x/dex/contract/market_order.go b/x/dex/contract/market_order.go new file mode 100644 index 0000000000..1c76ac2a10 --- /dev/null +++ b/x/dex/contract/market_order.go @@ -0,0 +1,68 @@ +package contract + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/x/dex/keeper" + dexkeeperabci "github.com/sei-protocol/sei-chain/x/dex/keeper/abci" + "github.com/sei-protocol/sei-chain/x/dex/types" + dextypesutils "github.com/sei-protocol/sei-chain/x/dex/types/utils" + "go.opentelemetry.io/otel/attribute" + otrace "go.opentelemetry.io/otel/trace" +) + +func PrepareCancelUnfulfilledMarketOrders( + ctx sdk.Context, + typedContractAddr dextypesutils.ContractAddress, + typedPairStr dextypesutils.PairString, + dexkeeper *keeper.Keeper, + orderIDToSettledQuantities map[uint64]sdk.Dec, +) { + dexkeeper.MemState.ClearCancellationForPair(ctx, typedContractAddr, typedPairStr) + for _, marketOrderID := range getUnfulfilledPlacedMarketOrderIds(ctx, typedContractAddr, typedPairStr, dexkeeper, orderIDToSettledQuantities) { + dexkeeper.MemState.GetBlockCancels(ctx, typedContractAddr, typedPairStr).Add(&types.Cancellation{ + Id: marketOrderID, + Initiator: types.CancellationInitiator_USER, + }) + } +} + +func getUnfulfilledPlacedMarketOrderIds( + ctx sdk.Context, + typedContractAddr dextypesutils.ContractAddress, + typedPairStr dextypesutils.PairString, + dexkeeper *keeper.Keeper, + orderIDToSettledQuantities map[uint64]sdk.Dec, +) []uint64 { + res := []uint64{} + for _, order := range dexkeeper.MemState.GetBlockOrders(ctx, typedContractAddr, typedPairStr).Get() { + if order.Status == types.OrderStatus_FAILED_TO_PLACE { + continue + } + if order.OrderType == types.OrderType_MARKET || order.OrderType == types.OrderType_LIQUIDATION { + if settledQuantity, ok := orderIDToSettledQuantities[order.Id]; !ok || settledQuantity.LT(order.Quantity) { + res = append(res, order.Id) + } + } + } + return res +} + +func CancelUnfulfilledMarketOrders( + ctx context.Context, + sdkCtx sdk.Context, + contractAddr string, + dexkeeper *keeper.Keeper, + registeredPairs []types.Pair, + tracer *otrace.Tracer, +) error { + spanCtx, span := (*tracer).Start(ctx, "CancelUnfulfilledMarketOrders") + span.SetAttributes(attribute.String("contract", contractAddr)) + defer span.End() + abciWrapper := dexkeeperabci.KeeperWrapper{Keeper: dexkeeper} + if err := abciWrapper.HandleEBCancelOrders(spanCtx, sdkCtx, tracer, contractAddr, registeredPairs); err != nil { + return err + } + return nil +} diff --git a/x/dex/contract/runner.go b/x/dex/contract/runner.go index 6aae908278..ed8fa439d9 100644 --- a/x/dex/contract/runner.go +++ b/x/dex/contract/runner.go @@ -83,7 +83,7 @@ func NewParallelRunner(runnable func(contract types.ContractInfo), contracts []t func (r *ParallelRunner) Run() { // The ordering of the two conditions below matters, since readyCnt // is updated before inProgressCnt. - for r.inProgressCnt > 0 || r.readyCnt > 0 { + for atomic.LoadInt64(&r.inProgressCnt) > 0 || atomic.LoadInt64(&r.readyCnt) > 0 { // r.readyContracts represent all frontier contracts that have // not started running yet. r.readyContracts.Range(func(key utils.ContractAddress, _ struct{}) bool { diff --git a/x/dex/exchange/limit_order_fuzz_test.go b/x/dex/exchange/limit_order_fuzz_test.go index 67c4d33050..d687d45c5a 100644 --- a/x/dex/exchange/limit_order_fuzz_test.go +++ b/x/dex/exchange/limit_order_fuzz_test.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/testutil/fuzzing" + "github.com/sei-protocol/sei-chain/utils/datastructures" "github.com/sei-protocol/sei-chain/x/dex/exchange" "github.com/sei-protocol/sei-chain/x/dex/types" "github.com/stretchr/testify/require" @@ -38,8 +39,8 @@ func fuzzTargetMatchLimitOrders( buyOrders := fuzzing.GetPlacedOrders(types.PositionDirection_LONG, types.OrderType_LIMIT, keepertest.TestPair, buyPrices, buyQuantities) sellOrders := fuzzing.GetPlacedOrders(types.PositionDirection_SHORT, types.OrderType_LIMIT, keepertest.TestPair, sellPrices, sellQuantities) orderBook := types.OrderBook{ - Longs: &types.CachedSortedOrderBookEntries{Entries: buyEntries, DirtyEntries: map[string]types.OrderBookEntry{}}, - Shorts: &types.CachedSortedOrderBookEntries{Entries: sellEntries, DirtyEntries: map[string]types.OrderBookEntry{}}, + Longs: &types.CachedSortedOrderBookEntries{Entries: buyEntries, DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry]()}, + Shorts: &types.CachedSortedOrderBookEntries{Entries: sellEntries, DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry]()}, } require.NotPanics(t, func() { exchange.MatchLimitOrders(TestFuzzLimitCtx, buyOrders, sellOrders, &orderBook) }) } diff --git a/x/dex/exchange/limit_order_test.go b/x/dex/exchange/limit_order_test.go index fd199cdc93..e54a73fafc 100644 --- a/x/dex/exchange/limit_order_test.go +++ b/x/dex/exchange/limit_order_test.go @@ -5,6 +5,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils/datastructures" "github.com/sei-protocol/sei-chain/x/dex/exchange" "github.com/sei-protocol/sei-chain/x/dex/types" "github.com/stretchr/testify/assert" @@ -53,11 +54,11 @@ func TestMatchSingleOrder(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -68,11 +69,11 @@ func TestMatchSingleOrder(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(1000)) assert.Equal(t, totalExecuted, sdk.NewDec(10)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 1) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 1) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 1) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 1) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -227,11 +228,11 @@ func TestAddOrders(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -242,15 +243,15 @@ func TestAddOrders(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(0)) assert.Equal(t, totalExecuted, sdk.NewDec(0)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 2) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 2) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("95").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 2) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 2) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("95").String()) assert.True(t, ok) - _, ok = orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("105").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("105").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("115").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("115").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -376,11 +377,11 @@ func TestMatchSingleOrderFromShortBook(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -391,11 +392,11 @@ func TestMatchSingleOrderFromShortBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(1000)) assert.Equal(t, totalExecuted, sdk.NewDec(10)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 1) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 1) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 1) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 1) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -473,11 +474,11 @@ func TestMatchSingleOrderFromLongBook(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -488,11 +489,11 @@ func TestMatchSingleOrderFromLongBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(1000)) assert.Equal(t, totalExecuted, sdk.NewDec(10)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 1) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 1) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 1) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 1) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -588,11 +589,11 @@ func TestMatchSingleOrderFromMultipleShortBook(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -603,13 +604,13 @@ func TestMatchSingleOrderFromMultipleShortBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(980)) assert.Equal(t, totalExecuted, sdk.NewDec(10)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 1) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 2) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 1) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 2) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("90").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("90").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -773,11 +774,11 @@ func TestMatchSingleOrderFromMultipleLongBook(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -788,13 +789,13 @@ func TestMatchSingleOrderFromMultipleLongBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(1020)) assert.Equal(t, totalExecuted, sdk.NewDec(10)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 2) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 1) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 2) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 1) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("110").String()] + _, ok = orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("110").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -980,11 +981,11 @@ func TestMatchMultipleOrderFromMultipleShortBook(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -995,17 +996,17 @@ func TestMatchMultipleOrderFromMultipleShortBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(1184)) assert.Equal(t, totalExecuted, sdk.NewDec(12)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 3) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 2) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("98").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 3) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 2) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("98").String()) assert.True(t, ok) - _, ok = orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("104").String()] + _, ok = orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("104").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("90").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("90").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries @@ -1232,11 +1233,11 @@ func TestMatchMultipleOrderFromMultipleLongBook(t *testing.T) { orderbook := types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } outcome := exchange.MatchLimitOrders( @@ -1247,17 +1248,17 @@ func TestMatchMultipleOrderFromMultipleLongBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(1216)) assert.Equal(t, totalExecuted, sdk.NewDec(12)) - assert.Equal(t, len(orderbook.Longs.DirtyEntries), 2) - assert.Equal(t, len(orderbook.Shorts.DirtyEntries), 3) - _, ok := orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, orderbook.Longs.DirtyEntries.Len(), 2) + assert.Equal(t, orderbook.Shorts.DirtyEntries.Len(), 3) + _, ok := orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Longs.DirtyEntries[sdk.MustNewDecFromStr("110").String()] + _, ok = orderbook.Longs.DirtyEntries.Load(sdk.MustNewDecFromStr("110").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("96").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("96").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = orderbook.Shorts.DirtyEntries[sdk.MustNewDecFromStr("102").String()] + _, ok = orderbook.Shorts.DirtyEntries.Load(sdk.MustNewDecFromStr("102").String()) assert.True(t, ok) longBook = orderbook.Longs.Entries shortBook = orderbook.Shorts.Entries diff --git a/x/dex/exchange/market_order.go b/x/dex/exchange/market_order.go index 0b48a96c07..c9a0031964 100644 --- a/x/dex/exchange/market_order.go +++ b/x/dex/exchange/market_order.go @@ -14,7 +14,8 @@ func MatchMarketOrders( totalExecuted, totalPrice := sdk.ZeroDec(), sdk.ZeroDec() settlements := []*types.SettlementEntry{} allTakerSettlements := []*types.SettlementEntry{} - for idx, marketOrder := range marketOrders { + for _, marketOrder := range marketOrders { + remainingQuantity := marketOrder.Quantity for i := range orderBookEntries.Entries { var existingOrder types.OrderBookEntry if direction == types.PositionDirection_LONG { @@ -36,12 +37,12 @@ func MatchMarketOrders( } } var executed sdk.Dec - if marketOrder.Quantity.LTE(existingOrder.GetEntry().Quantity) { - executed = marketOrder.Quantity + if remainingQuantity.LTE(existingOrder.GetEntry().Quantity) { + executed = remainingQuantity } else { executed = existingOrder.GetEntry().Quantity } - marketOrder.Quantity = marketOrder.Quantity.Sub(executed) + remainingQuantity = remainingQuantity.Sub(executed) totalExecuted = totalExecuted.Add(executed) totalPrice = totalPrice.Add( executed.Mul(existingOrder.GetPrice()), @@ -58,11 +59,10 @@ func MatchMarketOrders( settlements = append(settlements, makerSettlements...) // taker settlements' clearing price will need to be adjusted after all market order executions finish allTakerSettlements = append(allTakerSettlements, takerSettlements...) - if marketOrder.Quantity.IsZero() { + if remainingQuantity.IsZero() { break } } - marketOrders[idx].Quantity = marketOrder.Quantity } if totalExecuted.IsPositive() { clearingPrice := totalPrice.Quo(totalExecuted) diff --git a/x/dex/exchange/market_order_fuzz_test.go b/x/dex/exchange/market_order_fuzz_test.go index 813e8884e9..ac25e97f1a 100644 --- a/x/dex/exchange/market_order_fuzz_test.go +++ b/x/dex/exchange/market_order_fuzz_test.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/testutil/fuzzing" keepertest "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/utils/datastructures" "github.com/sei-protocol/sei-chain/x/dex/exchange" "github.com/sei-protocol/sei-chain/x/dex/types" "github.com/stretchr/testify/require" @@ -68,7 +69,7 @@ func fuzzTargetMatchMarketOrders( require.NotPanics(t, func() { exchange.MatchMarketOrders(TestFuzzMarketCtx, orders, &types.CachedSortedOrderBookEntries{ Entries: entries, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, direction) }) } diff --git a/x/dex/exchange/market_order_test.go b/x/dex/exchange/market_order_test.go index 1f69d85de2..c70b575bdd 100644 --- a/x/dex/exchange/market_order_test.go +++ b/x/dex/exchange/market_order_test.go @@ -5,6 +5,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils/datastructures" "github.com/sei-protocol/sei-chain/x/dex/exchange" "github.com/sei-protocol/sei-chain/x/dex/types" "github.com/stretchr/testify/assert" @@ -51,7 +52,7 @@ func TestMatchSingleMarketOrderFromShortBook(t *testing.T) { } entries := &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: make(map[string]types.OrderBookEntry), + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), } outcome := exchange.MatchMarketOrders( ctx, longOrders, entries, types.PositionDirection_LONG, @@ -61,8 +62,8 @@ func TestMatchSingleMarketOrderFromShortBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(500)) assert.Equal(t, totalExecuted, sdk.NewDec(5)) - assert.Equal(t, len(entries.DirtyEntries), 1) - _, ok := entries.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, entries.DirtyEntries.Len(), 1) + _, ok := entries.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) assert.Equal(t, len(shortBook), 1) assert.Equal(t, shortBook[0].GetPrice(), sdk.NewDec(100)) @@ -131,7 +132,7 @@ func TestMatchSingleMarketOrderFromLongBook(t *testing.T) { } entries := &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: make(map[string]types.OrderBookEntry), + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), } outcome := exchange.MatchMarketOrders( ctx, shortOrders, entries, types.PositionDirection_SHORT, @@ -141,8 +142,8 @@ func TestMatchSingleMarketOrderFromLongBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(500)) assert.Equal(t, totalExecuted, sdk.NewDec(5)) - assert.Equal(t, len(entries.DirtyEntries), 1) - _, ok := entries.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, entries.DirtyEntries.Len(), 1) + _, ok := entries.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) assert.Equal(t, len(longBook), 1) assert.Equal(t, longBook[0].GetPrice(), sdk.NewDec(100)) @@ -229,7 +230,7 @@ func TestMatchSingleMarketOrderFromMultipleShortBook(t *testing.T) { } entries := &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: make(map[string]types.OrderBookEntry), + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), } outcome := exchange.MatchMarketOrders( ctx, longOrders, entries, types.PositionDirection_LONG, @@ -239,10 +240,10 @@ func TestMatchSingleMarketOrderFromMultipleShortBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(480)) assert.Equal(t, totalExecuted, sdk.NewDec(5)) - assert.Equal(t, len(entries.DirtyEntries), 2) - _, ok := entries.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, entries.DirtyEntries.Len(), 2) + _, ok := entries.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = entries.DirtyEntries[sdk.MustNewDecFromStr("90").String()] + _, ok = entries.DirtyEntries.Load(sdk.MustNewDecFromStr("90").String()) assert.True(t, ok) assert.Equal(t, len(shortBook), 2) assert.Equal(t, shortBook[0].GetPrice(), sdk.NewDec(90)) @@ -397,7 +398,7 @@ func TestMatchSingleMarketOrderFromMultipleLongBook(t *testing.T) { } entries := &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: make(map[string]types.OrderBookEntry), + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), } outcome := exchange.MatchMarketOrders( ctx, shortOrders, entries, types.PositionDirection_SHORT, @@ -407,10 +408,10 @@ func TestMatchSingleMarketOrderFromMultipleLongBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(520)) assert.Equal(t, totalExecuted, sdk.NewDec(5)) - assert.Equal(t, len(entries.DirtyEntries), 2) - _, ok := entries.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, entries.DirtyEntries.Len(), 2) + _, ok := entries.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = entries.DirtyEntries[sdk.MustNewDecFromStr("110").String()] + _, ok = entries.DirtyEntries.Load(sdk.MustNewDecFromStr("110").String()) assert.True(t, ok) assert.Equal(t, len(longBook), 2) assert.Equal(t, longBook[0].GetPrice(), sdk.NewDec(100)) @@ -587,7 +588,7 @@ func TestMatchMultipleMarketOrderFromMultipleShortBook(t *testing.T) { } entries := &types.CachedSortedOrderBookEntries{ Entries: shortBook, - DirtyEntries: make(map[string]types.OrderBookEntry), + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), } outcome := exchange.MatchMarketOrders( ctx, longOrders, entries, types.PositionDirection_LONG, @@ -597,10 +598,10 @@ func TestMatchMultipleMarketOrderFromMultipleShortBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(580)) assert.Equal(t, totalExecuted, sdk.NewDec(6)) - assert.Equal(t, len(entries.DirtyEntries), 2) - _, ok := entries.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, entries.DirtyEntries.Len(), 2) + _, ok := entries.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = entries.DirtyEntries[sdk.MustNewDecFromStr("90").String()] + _, ok = entries.DirtyEntries.Load(sdk.MustNewDecFromStr("90").String()) assert.True(t, ok) assert.Equal(t, len(shortBook), 2) assert.Equal(t, shortBook[0].GetPrice(), sdk.NewDec(90)) @@ -803,7 +804,7 @@ func TestMatchMultipleMarketOrderFromMultipleLongBook(t *testing.T) { } entries := &types.CachedSortedOrderBookEntries{ Entries: longBook, - DirtyEntries: make(map[string]types.OrderBookEntry), + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), } outcome := exchange.MatchMarketOrders( ctx, shortOrders, entries, types.PositionDirection_SHORT, @@ -813,10 +814,10 @@ func TestMatchMultipleMarketOrderFromMultipleLongBook(t *testing.T) { settlements := outcome.Settlements assert.Equal(t, totalPrice, sdk.NewDec(620)) assert.Equal(t, totalExecuted, sdk.NewDec(6)) - assert.Equal(t, len(entries.DirtyEntries), 2) - _, ok := entries.DirtyEntries[sdk.MustNewDecFromStr("100").String()] + assert.Equal(t, entries.DirtyEntries.Len(), 2) + _, ok := entries.DirtyEntries.Load(sdk.MustNewDecFromStr("100").String()) assert.True(t, ok) - _, ok = entries.DirtyEntries[sdk.MustNewDecFromStr("110").String()] + _, ok = entries.DirtyEntries.Load(sdk.MustNewDecFromStr("110").String()) assert.True(t, ok) assert.Equal(t, len(longBook), 2) assert.Equal(t, longBook[0].GetPrice(), sdk.NewDec(100)) diff --git a/x/dex/keeper/utils/order_book.go b/x/dex/keeper/utils/order_book.go index 254cdff894..e28fd00c48 100644 --- a/x/dex/keeper/utils/order_book.go +++ b/x/dex/keeper/utils/order_book.go @@ -4,6 +4,7 @@ import ( "sort" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils/datastructures" "github.com/sei-protocol/sei-chain/x/dex/keeper" "github.com/sei-protocol/sei-chain/x/dex/types" dextypesutils "github.com/sei-protocol/sei-chain/x/dex/types/utils" @@ -22,11 +23,11 @@ func PopulateOrderbook( return &types.OrderBook{ Longs: &types.CachedSortedOrderBookEntries{ Entries: longs, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, Shorts: &types.CachedSortedOrderBookEntries{ Entries: shorts, - DirtyEntries: map[string]types.OrderBookEntry{}, + DirtyEntries: datastructures.NewTypedSyncMap[string, types.OrderBookEntry](), }, } } @@ -44,20 +45,20 @@ func FlushOrderbook( orderbook *types.OrderBook, ) { contractAddr := string(typedContractAddr) - for _, entry := range orderbook.Longs.DirtyEntries { + orderbook.Longs.DirtyEntries.DeepApply(func(entry types.OrderBookEntry) { if entry.GetEntry().Quantity.IsZero() { keeper.RemoveLongBookByPrice(ctx, contractAddr, entry.GetEntry().Price, entry.GetEntry().PriceDenom, entry.GetEntry().AssetDenom) } else { longOrder := entry.(*types.LongBook) keeper.SetLongBook(ctx, contractAddr, *longOrder) } - } - for _, entry := range orderbook.Shorts.DirtyEntries { + }) + orderbook.Shorts.DirtyEntries.DeepApply(func(entry types.OrderBookEntry) { if entry.GetEntry().Quantity.IsZero() { keeper.RemoveShortBookByPrice(ctx, contractAddr, entry.GetEntry().Price, entry.GetEntry().PriceDenom, entry.GetEntry().AssetDenom) } else { shortOrder := entry.(*types.ShortBook) keeper.SetShortBook(ctx, contractAddr, *shortOrder) } - } + }) } diff --git a/x/dex/types/orders.go b/x/dex/types/orders.go index a3909e7e6b..f688eff796 100644 --- a/x/dex/types/orders.go +++ b/x/dex/types/orders.go @@ -1,25 +1,44 @@ package types -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/datastructures" +) type OrderBook struct { Longs *CachedSortedOrderBookEntries Shorts *CachedSortedOrderBookEntries } +func (o *OrderBook) DeepCopy() *OrderBook { + return &OrderBook{ + Longs: o.Longs.DeepCopy(), + Shorts: o.Shorts.DeepCopy(), + } +} + // entries are always sorted by prices in ascending order, regardless of side type CachedSortedOrderBookEntries struct { Entries []OrderBookEntry - DirtyEntries map[string]OrderBookEntry + DirtyEntries *datastructures.TypedSyncMap[string, OrderBookEntry] +} + +func (c *CachedSortedOrderBookEntries) DeepCopy() *CachedSortedOrderBookEntries { + return &CachedSortedOrderBookEntries{ + Entries: utils.Map(c.Entries, func(e OrderBookEntry) OrderBookEntry { return e.DeepCopy() }), + DirtyEntries: c.DirtyEntries.DeepCopy(func(e OrderBookEntry) OrderBookEntry { return e.DeepCopy() }), + } } func (c *CachedSortedOrderBookEntries) AddDirtyEntry(entry OrderBookEntry) { - c.DirtyEntries[entry.GetPrice().String()] = entry + c.DirtyEntries.Store(entry.GetPrice().String(), entry) } type OrderBookEntry interface { GetPrice() sdk.Dec GetEntry() *OrderEntry + DeepCopy() OrderBookEntry } func (m *LongBook) GetPrice() sdk.Dec { @@ -29,9 +48,53 @@ func (m *LongBook) GetPrice() sdk.Dec { return sdk.ZeroDec() } +func (m *LongBook) DeepCopy() OrderBookEntry { + allocations := []*Allocation{} + for _, allo := range m.Entry.Allocations { + allocations = append(allocations, &Allocation{ + OrderId: allo.OrderId, + Quantity: allo.Quantity, + Account: allo.Account, + }) + } + newOrderEntry := OrderEntry{ + Price: m.Entry.Price, + Quantity: m.Entry.Quantity, + PriceDenom: m.Entry.PriceDenom, + AssetDenom: m.Entry.AssetDenom, + Allocations: allocations, + } + return &LongBook{ + Price: m.Price, + Entry: &newOrderEntry, + } +} + func (m *ShortBook) GetPrice() sdk.Dec { if m != nil { return m.Price } return sdk.ZeroDec() } + +func (m *ShortBook) DeepCopy() OrderBookEntry { + allocations := []*Allocation{} + for _, allo := range m.Entry.Allocations { + allocations = append(allocations, &Allocation{ + OrderId: allo.OrderId, + Quantity: allo.Quantity, + Account: allo.Account, + }) + } + newOrderEntry := OrderEntry{ + Price: m.Entry.Price, + Quantity: m.Entry.Quantity, + PriceDenom: m.Entry.PriceDenom, + AssetDenom: m.Entry.AssetDenom, + Allocations: allocations, + } + return &ShortBook{ + Price: m.Price, + Entry: &newOrderEntry, + } +} diff --git a/x/dex/types/wasm/block_hooks.go b/x/dex/types/wasm/block_hooks.go index 36270eb798..3e1f6558e4 100644 --- a/x/dex/types/wasm/block_hooks.go +++ b/x/dex/types/wasm/block_hooks.go @@ -1,6 +1,8 @@ package wasm import ( + "sync" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/x/dex/types" ) @@ -25,7 +27,9 @@ func NewSudoFinalizeBlockMsg() *SudoFinalizeBlockMsg { } } -func (m *SudoFinalizeBlockMsg) AddContractResult(result ContractOrderResult) { +func (m *SudoFinalizeBlockMsg) AddContractResult(result ContractOrderResult, mu *sync.Mutex) { + mu.Lock() + defer mu.Unlock() m.FinalizeBlock = FinalizeBlockRequest{ Results: append(m.FinalizeBlock.Results, result), }