From 9b60f430ca507c16b3ba73b5bd0a3f661a4aa6eb Mon Sep 17 00:00:00 2001 From: king Date: Mon, 19 Oct 2026 16:27:38 +0800 Subject: [PATCH] fix(trade): reject order amount overflow and validate price symbol Two fund-safety defects in the trade executor: 1. Order amounts are multiplied as plain int64. A sell order with TotalBoardlot*AmountPerBoardlot wrapping past int64 freezes only the wrapped remainder, and a buy-limit order whose PricePerBoardlot*TotalBoardlot wraps can later pay the taker seller the wrapped value in tradeSellMarket while the buyer receives the full token amount. A malicious buyer can therefore take a seller's tokens for a tiny wrapped payment. 2. checkPrice/notSameAsset were called with PriceExec in the symbol argument, so the same-asset order check and price-pair validation were ineffective. - Add isMulOverflow and check every order/payment/token multiplication involved in tradeSell, tradeBuyLimit, tradeBuy and tradeSellMarket. - Pass PriceSymbol into checkPrice/notSameAsset after the new fork. - Behavior is gated behind ForkTradeFixOverflow (default height 0). --- chain33.fork.toml | 1 + chain33.para.toml | 1 + .../dapp/evm/cmd/ci2/chain33.proxyminer.toml | 1 + .../trade/executor/trade_fix_overflow_test.go | 364 ++++++++++++++++++ plugin/dapp/trade/executor/tradedb.go | 49 ++- plugin/dapp/trade/executor/util.go | 5 + plugin/dapp/trade/types/const.go | 3 + plugin/dapp/trade/types/trade.go | 1 + 8 files changed, 418 insertions(+), 7 deletions(-) create mode 100644 plugin/dapp/trade/executor/trade_fix_overflow_test.go diff --git a/chain33.fork.toml b/chain33.fork.toml index 79c79eece..90e9ce5aa 100644 --- a/chain33.fork.toml +++ b/chain33.fork.toml @@ -241,6 +241,7 @@ ForkTradeAsset=1010000 ForkTradeID=1450000 ForkTradeFixAssetDB=2500000 ForkTradePrice=3150000 +ForkTradeFixOverflow=0 [fork.sub.unfreeze] Enable=0 diff --git a/chain33.para.toml b/chain33.para.toml index 00afc4248..1d0f4a2be 100644 --- a/chain33.para.toml +++ b/chain33.para.toml @@ -394,6 +394,7 @@ ForkTradeAsset= 0 ForkTradeID = 0 ForkTradeFixAssetDB = 0 ForkTradePrice = 0 +ForkTradeFixOverflow = 0 [fork.sub.paracross] Enable=0 diff --git a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml index 1316dafbb..d9d4715bd 100644 --- a/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml +++ b/plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml @@ -746,6 +746,7 @@ ForkTradeAsset=1010000 ForkTradeID=1450000 ForkTradeFixAssetDB=2500000 ForkTradePrice=3150000 +ForkTradeFixOverflow=0 [fork.sub.unfreeze] Enable=0 diff --git a/plugin/dapp/trade/executor/trade_fix_overflow_test.go b/plugin/dapp/trade/executor/trade_fix_overflow_test.go new file mode 100644 index 000000000..478f8493b --- /dev/null +++ b/plugin/dapp/trade/executor/trade_fix_overflow_test.go @@ -0,0 +1,364 @@ +// Copyright Fuzamei Corp. 2018 All Rights Reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package executor + +import ( + "strings" + "testing" + + "github.com/33cn/chain33/account" + apimock "github.com/33cn/chain33/client/mocks" + "github.com/33cn/chain33/common/address" + dbm "github.com/33cn/chain33/common/db" + drivers "github.com/33cn/chain33/system/dapp" + "github.com/33cn/chain33/types" + "github.com/33cn/chain33/util" + pty "github.com/33cn/plugin/plugin/dapp/trade/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func newTradeFixCfg() *types.Chain33Config { + return types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"chain33\"", 1)) +} + +func newTradeFixDriver(cfg *types.Chain33Config, height int64, stateDB dbm.KV, localDB dbm.KVDB) drivers.Driver { + api := new(apimock.QueueProtocolAPI) + api.On("GetConfig", mock.Anything).Return(cfg, nil) + driver := newTrade() + driver.SetAPI(api) + driver.SetEnv(height, 1539918074, 1539918074) + driver.SetStateDB(stateDB) + driver.SetLocalDB(localDB) + return driver +} + +func applyTradeFixReceipt(stateDB dbm.KV, receipt *types.Receipt) { + for _, kv := range receipt.KV { + stateDB.Set(kv.Key, kv.Value) + } +} + +func saveTradeFixExecAccount(t *testing.T, cfg *types.Chain33Config, kvdb dbm.KV, exec, symbol, addr string, balance int64) *account.DB { + var acc *account.DB + if exec == "coins" { + acc = account.NewCoinsAccount(cfg) + acc.SetDB(kvdb) + } else { + var err error + acc, err = account.NewAccountDB(cfg, exec, symbol, kvdb) + require.Nil(t, err) + } + acc.SaveExecAccount(address.ExecAddress("trade"), &types.Account{Balance: balance, Addr: addr}) + return acc +} + +// sell order frozen amount TotalBoardlot*AmountPerBoardlot int64 overflow must be rejected after ForkTradeFixOverflow +func TestTradeFixSellOverflow(t *testing.T) { + cfg := newTradeFixCfg() + height := int64(10) + execAddr := address.ExecAddress("trade") + total := int64(10000000000) + + _, ldb, kvdb := util.CreateTestDB() + defer ldb.Close() + + accToken := saveTradeFixExecAccount(t, cfg, kvdb, "token", "TEST", string(Nodes[0]), total) + driver := newTradeFixDriver(cfg, height, kvdb, kvdb) + + // nominal total (2^32+1)*2^32 overflows int64 and wraps to 2^32 + sell := &pty.TradeSellTx{ + TokenSymbol: "TEST", + AssetExec: "token", + AmountPerBoardlot: 1 << 32, + TotalBoardlot: (1 << 32) + 1, + MinBoardlot: 0, + PricePerBoardlot: 2, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err := pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + _, err = driver.Exec(tx, 0) + assert.Equal(t, types.ErrInvalidParam, err, "overflow sell order must be rejected after fork") + + acc := accToken.LoadExecAccount(string(Nodes[0]), execAddr) + assert.Equal(t, total, acc.Balance) + assert.Equal(t, int64(0), acc.Frozen) + + // normal sell order is not affected + sell = &pty.TradeSellTx{ + TokenSymbol: "TEST", + AssetExec: "token", + AmountPerBoardlot: 100, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 2, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err = pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + receipt, err := driver.Exec(tx, 0) + require.Nil(t, err, "normal sell order must succeed after fork") + applyTradeFixReceipt(kvdb, receipt) + + acc = accToken.LoadExecAccount(string(Nodes[0]), execAddr) + assert.Equal(t, total-100*100, acc.Balance) + assert.Equal(t, int64(100*100), acc.Frozen) +} + +// buy limit order frozen amount PricePerBoardlot*TotalBoardlot int64 overflow must be rejected after ForkTradeFixOverflow +func TestTradeFixBuyLimitOverflow(t *testing.T) { + cfg := newTradeFixCfg() + height := int64(10) + execAddr := address.ExecAddress("trade") + total := int64(10000000000) + + _, ldb, kvdb := util.CreateTestDB() + defer ldb.Close() + + accBty := saveTradeFixExecAccount(t, cfg, kvdb, "coins", "bty", string(Nodes[0]), total) + driver := newTradeFixDriver(cfg, height, kvdb, kvdb) + + // nominal cost 2^32*(2^32+1) overflows int64 and wraps to 2^32 + buy := &pty.TradeBuyLimitTx{ + TokenSymbol: "TEST", + AssetExec: "paracross", // skip token exist check + AmountPerBoardlot: 2, + TotalBoardlot: (1 << 32) + 1, + MinBoardlot: 0, + PricePerBoardlot: 1 << 32, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err := pty.CreateRawTradeBuyLimitTx(cfg, buy) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + _, err = driver.Exec(tx, 0) + assert.Equal(t, types.ErrInvalidParam, err, "overflow buy limit order must be rejected after fork") + + acc := accBty.LoadExecAccount(string(Nodes[0]), execAddr) + assert.Equal(t, total, acc.Balance) + assert.Equal(t, int64(0), acc.Frozen) + + // normal buy limit order is not affected + buy = &pty.TradeBuyLimitTx{ + TokenSymbol: "TEST", + AssetExec: "paracross", + AmountPerBoardlot: 2, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 100, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err = pty.CreateRawTradeBuyLimitTx(cfg, buy) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + receipt, err := driver.Exec(tx, 0) + require.Nil(t, err, "normal buy limit order must succeed after fork") + applyTradeFixReceipt(kvdb, receipt) + + acc = accBty.LoadExecAccount(string(Nodes[0]), execAddr) + assert.Equal(t, total-100*100, acc.Balance) + assert.Equal(t, int64(100*100), acc.Frozen) +} + +// checkPrice/notSameAsset were wrongly called with PriceExec as the symbol argument, +// after ForkTradeFixOverflow PriceSymbol must be validated instead +func TestTradeFixPriceSymbolArg(t *testing.T) { + cfg := newTradeFixCfg() + height := int64(10) + total := int64(10000000000) + + _, ldb, kvdb := util.CreateTestDB() + defer ldb.Close() + + saveTradeFixExecAccount(t, cfg, kvdb, "coins", "bty", string(Nodes[0]), total) + driver := newTradeFixDriver(cfg, height, kvdb, kvdb) + + // same asset order: sell bty priced in bty, must be rejected by notSameAsset + sell := &pty.TradeSellTx{ + TokenSymbol: "bty", + AssetExec: "coins", + AmountPerBoardlot: 100, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 1, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err := pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + _, err = driver.Exec(tx, 0) + assert.Equal(t, pty.ErrAssetAndPriceSame, err, "same asset sell order must be rejected after fork") + + // price exec set but price symbol missing, must be rejected by checkPrice + sell = &pty.TradeSellTx{ + TokenSymbol: "bty", + AssetExec: "coins", + AmountPerBoardlot: 100, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 1, + PriceExec: "coins", + PriceSymbol: "", + } + tx, err = pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + _, err = driver.Exec(tx, 0) + assert.Equal(t, types.ErrInvalidParam, err, "order with empty price symbol must be rejected after fork") + + // same asset buy limit order must be rejected as well + buy := &pty.TradeBuyLimitTx{ + TokenSymbol: "bty", + AssetExec: "coins", + AmountPerBoardlot: 2, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 100, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err = pty.CreateRawTradeBuyLimitTx(cfg, buy) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + _, err = driver.Exec(tx, 0) + assert.Equal(t, pty.ErrAssetAndPriceSame, err, "same asset buy limit order must be rejected after fork") +} + +// before ForkTradeFixOverflow the old vulnerable behavior must be kept for consensus +func TestTradeFixPreForkBehavior(t *testing.T) { + cfg := newTradeFixCfg() + cfg.SetDappFork(pty.TradeX, pty.ForkTradeFixOverflowX, int64(100)) + height := int64(99) + execAddr := address.ExecAddress("trade") + total := int64(10000000000) + + _, ldb, kvdb := util.CreateTestDB() + defer ldb.Close() + + accToken := saveTradeFixExecAccount(t, cfg, kvdb, "token", "TEST", string(Nodes[0]), total) + saveTradeFixExecAccount(t, cfg, kvdb, "coins", "bty", string(Nodes[0]), total) + driver := newTradeFixDriver(cfg, height, kvdb, kvdb) + + // overflow sell order is accepted before the fork, frozen amount wraps to 2^32 + sell := &pty.TradeSellTx{ + TokenSymbol: "TEST", + AssetExec: "token", + AmountPerBoardlot: 1 << 32, + TotalBoardlot: (1 << 32) + 1, + MinBoardlot: 0, + PricePerBoardlot: 2, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err := pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + receipt, err := driver.Exec(tx, 0) + require.Nil(t, err, "overflow sell order keeps old behavior before fork") + applyTradeFixReceipt(kvdb, receipt) + + acc := accToken.LoadExecAccount(string(Nodes[0]), execAddr) + assert.Equal(t, int64(1<<32), acc.Frozen, "frozen amount wraps to 2^32 before fork") + + // same asset order is accepted before the fork + sell = &pty.TradeSellTx{ + TokenSymbol: "bty", + AssetExec: "coins", + AmountPerBoardlot: 100, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 1, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err = pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + _, err = driver.Exec(tx, 0) + require.Nil(t, err, "same asset sell order keeps old behavior before fork") +} + +// normal sell/buy/revoke flow must not be affected by ForkTradeFixOverflow +func TestTradeFixNormalFlow(t *testing.T) { + cfg := newTradeFixCfg() + height := int64(10) + execAddr := address.ExecAddress("trade") + total := int64(100000) + + _, ldb, kvdb := util.CreateTestDB() + defer ldb.Close() + + accToken := saveTradeFixExecAccount(t, cfg, kvdb, "token", "TEST", string(Nodes[0]), total) + accBty := saveTradeFixExecAccount(t, cfg, kvdb, "coins", "bty", string(Nodes[1]), total) + driver := newTradeFixDriver(cfg, height, kvdb, kvdb) + + sell := &pty.TradeSellTx{ + TokenSymbol: "TEST", + AssetExec: "token", + AmountPerBoardlot: 100, + TotalBoardlot: 100, + MinBoardlot: 1, + PricePerBoardlot: 2, + PriceExec: "coins", + PriceSymbol: "bty", + } + tx, err := pty.CreateRawTradeSellTx(cfg, sell) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + receipt, err := driver.Exec(tx, 0) + require.Nil(t, err) + applyTradeFixReceipt(kvdb, receipt) + + var sellOrder pty.SellOrder + require.Nil(t, types.Decode(receipt.KV[len(receipt.KV)-1].Value, &sellOrder)) + sellID := sellOrder.SellID[len("mavl-trade-sell-"):] + + // buy 5 boardlots + buy := &pty.TradeBuyTx{SellID: sellID, BoardlotCnt: 5} + tx, err = pty.CreateRawTradeBuyTx(cfg, buy) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyB) + require.Nil(t, err) + receipt, err = driver.Exec(tx, 0) + require.Nil(t, err, "buy must succeed after fork") + applyTradeFixReceipt(kvdb, receipt) + + require.Nil(t, types.Decode(receipt.KV[len(receipt.KV)-1].Value, &sellOrder)) + assert.Equal(t, int64(5), sellOrder.SoldBoardlot) + + // revoke the remaining order + revoke := &pty.TradeRevokeTx{SellID: sellID} + tx, err = pty.CreateRawTradeRevokeTx(cfg, revoke) + require.Nil(t, err) + tx, err = signTx(tx, PrivKeyA) + require.Nil(t, err) + receipt, err = driver.Exec(tx, 0) + require.Nil(t, err, "revoke must succeed after fork") + applyTradeFixReceipt(kvdb, receipt) + + acc := accToken.LoadExecAccount(string(Nodes[0]), execAddr) + assert.Equal(t, total-5*100, acc.Balance) + assert.Equal(t, int64(0), acc.Frozen) + assert.Equal(t, total-5*2, accBty.LoadExecAccount(string(Nodes[1]), execAddr).Balance) +} diff --git a/plugin/dapp/trade/executor/tradedb.go b/plugin/dapp/trade/executor/tradedb.go index 0825c5d11..7e1756ffe 100644 --- a/plugin/dapp/trade/executor/tradedb.go +++ b/plugin/dapp/trade/executor/tradedb.go @@ -248,10 +248,15 @@ func (action *tradeAction) tradeSell(sell *pty.TradeForSell) (*types.Receipt, er if !checkAsset(cfg, action.height, sell.AssetExec, sell.TokenSymbol) { return nil, types.ErrInvalidParam } - if !checkPrice(cfg, action.height, sell.PriceExec, sell.PriceExec) { + fixOverflow := cfg.IsDappFork(action.height, pty.TradeX, pty.ForkTradeFixOverflowX) + priceSymbol := sell.PriceExec + if fixOverflow { + priceSymbol = sell.PriceSymbol + } + if !checkPrice(cfg, action.height, sell.PriceExec, priceSymbol) { return nil, types.ErrInvalidParam } - if !notSameAsset(cfg, action.height, sell.AssetExec, sell.TokenSymbol, sell.PriceExec, sell.PriceExec) { + if !notSameAsset(cfg, action.height, sell.AssetExec, sell.TokenSymbol, sell.PriceExec, priceSymbol) { return nil, pty.ErrAssetAndPriceSame } @@ -260,6 +265,11 @@ func (action *tradeAction) tradeSell(sell *pty.TradeForSell) (*types.Receipt, er return nil, err } //确认发起此次出售或者众筹的余额是否足够 + if fixOverflow && isMulOverflow(sell.GetTotalBoardlot(), sell.GetAmountPerBoardlot()) { + tradelog.Error("trade sell overflow", "addr", action.fromaddr, + "totalBoardlot", sell.GetTotalBoardlot(), "amountPerBoardlot", sell.GetAmountPerBoardlot()) + return nil, types.ErrInvalidParam + } totalAmount := sell.GetTotalBoardlot() * sell.GetAmountPerBoardlot() receipt, err := accDB.ExecFrozen(action.fromaddr, action.execaddr, totalAmount) if err != nil { @@ -334,11 +344,19 @@ func (action *tradeAction) tradeBuy(buyOrder *pty.TradeForBuy) (*types.Receipt, "price", sellOrder.PriceExec+"-"+sellOrder.PriceSymbol, "err", err) return nil, err } + if cfg.IsDappFork(action.height, pty.TradeX, pty.ForkTradeFixOverflowX) && + (isMulOverflow(buyOrder.BoardlotCnt, sellOrder.PricePerBoardlot) || + isMulOverflow(buyOrder.BoardlotCnt, sellOrder.AmountPerBoardlot)) { + tradelog.Error("trade buy overflow", "addr", action.fromaddr, "boardlotCnt", buyOrder.BoardlotCnt, + "price", sellOrder.PricePerBoardlot, "amountPerBoardlot", sellOrder.AmountPerBoardlot) + return nil, types.ErrInvalidParam + } + payAmount := buyOrder.BoardlotCnt * sellOrder.PricePerBoardlot //首先购买费用的划转 - receiptFromAcc, err := priceAcc.ExecTransfer(action.fromaddr, sellOrder.Address, action.execaddr, buyOrder.BoardlotCnt*sellOrder.PricePerBoardlot) + receiptFromAcc, err := priceAcc.ExecTransfer(action.fromaddr, sellOrder.Address, action.execaddr, payAmount) if err != nil { tradelog.Error("account.Transfer ", "addrFrom", action.fromaddr, "addrTo", sellOrder.Address, - "amount", buyOrder.BoardlotCnt*sellOrder.PricePerBoardlot) + "amount", payAmount) return nil, err } //然后实现购买token的转移,因为这部分token在之前的卖单生成时已经进行冻结 @@ -354,7 +372,7 @@ func (action *tradeAction) tradeBuy(buyOrder *pty.TradeForBuy) (*types.Receipt, "addrTo", action.fromaddr, "execaddr", action.execaddr, "amount", buyOrder.BoardlotCnt*sellOrder.AmountPerBoardlot) //因为未能成功将对应的token进行转账,所以需要将购买方的账户资金进行回退 - priceAcc.ExecTransfer(sellOrder.Address, action.fromaddr, action.execaddr, buyOrder.BoardlotCnt*sellOrder.PricePerBoardlot) + priceAcc.ExecTransfer(sellOrder.Address, action.fromaddr, action.execaddr, payAmount) return nil, err } @@ -464,10 +482,15 @@ func (action *tradeAction) tradeBuyLimit(buy *pty.TradeForBuyLimit) (*types.Rece if !checkAsset(cfg, action.height, buy.AssetExec, buy.TokenSymbol) { return nil, types.ErrInvalidParam } - if !checkPrice(cfg, action.height, buy.PriceExec, buy.PriceExec) { + fixOverflow := cfg.IsDappFork(action.height, pty.TradeX, pty.ForkTradeFixOverflowX) + priceSymbol := buy.PriceExec + if fixOverflow { + priceSymbol = buy.PriceSymbol + } + if !checkPrice(cfg, action.height, buy.PriceExec, priceSymbol) { return nil, types.ErrInvalidParam } - if !notSameAsset(cfg, action.height, buy.AssetExec, buy.TokenSymbol, buy.PriceExec, buy.PriceExec) { + if !notSameAsset(cfg, action.height, buy.AssetExec, buy.TokenSymbol, buy.PriceExec, priceSymbol) { return nil, pty.ErrAssetAndPriceSame } @@ -476,6 +499,11 @@ func (action *tradeAction) tradeBuyLimit(buy *pty.TradeForBuyLimit) (*types.Rece return nil, err } // check enough bty + if fixOverflow && isMulOverflow(buy.PricePerBoardlot, buy.TotalBoardlot) { + tradelog.Error("trade buyLimit overflow", "addr", action.fromaddr, + "pricePerBoardlot", buy.PricePerBoardlot, "totalBoardlot", buy.TotalBoardlot) + return nil, types.ErrInvalidParam + } amount := buy.PricePerBoardlot * buy.TotalBoardlot receipt, err := priceAcc.ExecFrozen(action.fromaddr, action.execaddr, amount) if err != nil { @@ -544,6 +572,13 @@ func (action *tradeAction) tradeSellMarket(sellOrder *pty.TradeForSellMarket) (* tradelog.Error("createAccountDB failed", "err", err, "order", buyOrder) return nil, err } + if cfg.IsDappFork(action.height, pty.TradeX, pty.ForkTradeFixOverflowX) && + (isMulOverflow(sellOrder.BoardlotCnt, buyOrder.AmountPerBoardlot) || + isMulOverflow(sellOrder.BoardlotCnt, buyOrder.PricePerBoardlot)) { + tradelog.Error("trade sellMarket overflow", "addr", action.fromaddr, "boardlotCnt", sellOrder.BoardlotCnt, + "amountPerBoardlot", buyOrder.AmountPerBoardlot, "price", buyOrder.PricePerBoardlot) + return nil, types.ErrInvalidParam + } amountToken := sellOrder.BoardlotCnt * buyOrder.AmountPerBoardlot tradelog.Debug("tradeSellMarket", "step1 cnt", sellOrder.BoardlotCnt, "amountToken", amountToken) receiptFromExecAcc, err := accDB.ExecTransfer(action.fromaddr, buyOrder.Address, action.execaddr, amountToken) diff --git a/plugin/dapp/trade/executor/util.go b/plugin/dapp/trade/executor/util.go index c6dc6ec9b..9ea78d851 100644 --- a/plugin/dapp/trade/executor/util.go +++ b/plugin/dapp/trade/executor/util.go @@ -69,6 +69,11 @@ func notSameAsset(cfg *types.Chain33Config, height int64, assetExec, assetSymbol return true } +// isMulOverflow checks whether a*b overflows int64, a and b must be non-negative +func isMulOverflow(a, b int64) bool { + return a != 0 && (a*b)/a != b +} + func createAccountDB(cfg *types.Chain33Config, height int64, db db.KV, exec, symbol string) (*account.DB, error) { if cfg.IsDappFork(height, pt.TradeX, pt.ForkTradeFixAssetDBX) { if exec == "" { diff --git a/plugin/dapp/trade/types/const.go b/plugin/dapp/trade/types/const.go index fcc12e8eb..55d00866d 100644 --- a/plugin/dapp/trade/types/const.go +++ b/plugin/dapp/trade/types/const.go @@ -94,4 +94,7 @@ const ( ForkTradeFixAssetDBX = "ForkTradeFixAssetDB" // ForkTradePriceX all asset can be price ForkTradePriceX = "ForkTradePrice" + // ForkTradeFixOverflowX fix bug: frozen amount int64 overflow in tradeSell/tradeBuyLimit/tradeBuy/tradeSellMarket, + // and checkPrice/notSameAsset wrongly called with PriceExec instead of PriceSymbol + ForkTradeFixOverflowX = "ForkTradeFixOverflow" ) diff --git a/plugin/dapp/trade/types/trade.go b/plugin/dapp/trade/types/trade.go index a54ff7f82..475ce8af5 100644 --- a/plugin/dapp/trade/types/trade.go +++ b/plugin/dapp/trade/types/trade.go @@ -65,6 +65,7 @@ func InitFork(cfg *types.Chain33Config) { cfg.RegisterDappFork(TradeX, ForkTradeIDX, 0) cfg.RegisterDappFork(TradeX, ForkTradeFixAssetDBX, 0) cfg.RegisterDappFork(TradeX, ForkTradePriceX, 0) + cfg.RegisterDappFork(TradeX, ForkTradeFixOverflowX, 0) } // InitExecutor ...