Unverified Commit 3f71a8a5 authored by vipwzw's avatar vipwzw Committed by GitHub

Merge pull request #260 from linj-disanbo/trade-localdb

Trade localdb
parents 556879c1 8c429796
...@@ -16,7 +16,7 @@ index: addr,status,addr_status,admin,admin_status,category_status ...@@ -16,7 +16,7 @@ index: addr,status,addr_status,admin,admin_status,category_status
*/ */
var opt_guess_user = &table.Option{ var opt_guess_user = &table.Option{
Prefix: "LODB_guess", Prefix: "LODB-guess",
Name: "user", Name: "user",
Primary: "index", Primary: "index",
Index: []string{"addr", "startindex"}, Index: []string{"addr", "startindex"},
...@@ -70,7 +70,7 @@ func (tx *GuessUserRow) Get(key string) ([]byte, error) { ...@@ -70,7 +70,7 @@ func (tx *GuessUserRow) Get(key string) ([]byte, error) {
} }
var opt_guess_game = &table.Option{ var opt_guess_game = &table.Option{
Prefix: "LODB_guess", Prefix: "LODB-guess",
Name: "game", Name: "game",
Primary: "startindex", Primary: "startindex",
Index: []string{"gameid", "status", "admin", "admin_status", "category_status"}, Index: []string{"gameid", "status", "admin", "admin_status", "category_status"},
......
...@@ -30,6 +30,10 @@ func TradeCmd() *cobra.Command { ...@@ -30,6 +30,10 @@ func TradeCmd() *cobra.Command {
CreateRawTradeBuyTxCmd(), CreateRawTradeBuyTxCmd(),
CreateRawTradeRevokeTxCmd(), CreateRawTradeRevokeTxCmd(),
CreateRawBuyLimitTxCmd(),
CreateRawSellMarketTxCmd(),
CreateRawBuyRevokeTxCmd(),
ShowOnesSellOrdersCmd(), ShowOnesSellOrdersCmd(),
ShowOnesSellOrdersStatusCmd(), ShowOnesSellOrdersStatusCmd(),
ShowTokenSellOrdersStatusCmd(), ShowTokenSellOrdersStatusCmd(),
...@@ -555,3 +559,126 @@ func tokenSellRevoke(cmd *cobra.Command, args []string) { ...@@ -555,3 +559,126 @@ func tokenSellRevoke(cmd *cobra.Command, args []string) {
ctx := jsonrpc.NewRPCCtx(rpcLaddr, "trade.CreateRawTradeRevokeTx", params, nil) ctx := jsonrpc.NewRPCCtx(rpcLaddr, "trade.CreateRawTradeRevokeTx", params, nil)
ctx.RunWithoutMarshal() ctx.RunWithoutMarshal()
} }
// BuyLimit SellMarket BuyRevoke transactions
// CreateRawBuyLimitTxCmd : create raw buy limit token transaction
func CreateRawBuyLimitTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "buy_limit",
Short: "Create a buy limit transaction",
Run: tokenBuyLimit,
}
addTokenBuyLimitFlags(cmd)
return cmd
}
func addTokenBuyLimitFlags(cmd *cobra.Command) {
cmd.Flags().StringP("symbol", "s", "", "token symbol")
cmd.MarkFlagRequired("symbol")
cmd.Flags().Int64P("min", "m", 0, "min boardlot")
cmd.MarkFlagRequired("min")
cmd.Flags().Float64P("price", "p", 0, "price per boardlot")
cmd.MarkFlagRequired("price")
cmd.Flags().Float64P("fee", "f", 0, "transaction fee")
cmd.Flags().Float64P("total", "t", 0, "total tokens to buy")
cmd.MarkFlagRequired("total")
}
func tokenBuyLimit(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
symbol, _ := cmd.Flags().GetString("symbol")
min, _ := cmd.Flags().GetInt64("min")
price, _ := cmd.Flags().GetFloat64("price")
fee, _ := cmd.Flags().GetFloat64("fee")
total, _ := cmd.Flags().GetFloat64("total")
priceInt64 := int64(price * 1e4)
feeInt64 := int64(fee * 1e4)
totalInt64 := int64(total * 1e8 / 1e6)
params := &pty.TradeBuyLimitTx{
TokenSymbol: symbol,
AmountPerBoardlot: 1e6,
MinBoardlot: min,
PricePerBoardlot: priceInt64 * 1e4,
TotalBoardlot: totalInt64,
Fee: feeInt64 * 1e4,
AssetExec: "token",
}
ctx := jsonrpc.NewRPCCtx(rpcLaddr, "trade.CreateRawTradeBuyLimitTx", params, nil)
ctx.RunWithoutMarshal()
}
// CreateRawSellMarketTxCmd : create raw sell market token transaction
func CreateRawSellMarketTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "sell_market",
Short: "Create a sell market transaction",
Run: sellMarket,
}
addSellMarketFlags(cmd)
return cmd
}
func addSellMarketFlags(cmd *cobra.Command) {
cmd.Flags().StringP("buy_id", "b", "", "buy id")
cmd.MarkFlagRequired("buy_id")
cmd.Flags().Int64P("count", "c", 0, "count of selling (boardlot)")
cmd.MarkFlagRequired("count")
cmd.Flags().Float64P("fee", "f", 0, "transaction fee")
}
func sellMarket(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
buyID, _ := cmd.Flags().GetString("buy_id")
fee, _ := cmd.Flags().GetFloat64("fee")
count, _ := cmd.Flags().GetInt64("count")
feeInt64 := int64(fee * 1e4)
params := &pty.TradeSellMarketTx{
BuyID: buyID,
BoardlotCnt: count,
Fee: feeInt64 * 1e4,
}
ctx := jsonrpc.NewRPCCtx(rpcLaddr, "trade.CreateRawTradeSellMarketTx", params, nil)
ctx.RunWithoutMarshal()
}
// CreateRawBuyRevokeTxCmd : create raw buy revoke transaction
func CreateRawBuyRevokeTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "revoke_buy",
Short: "Create a revoke buy limit transaction",
Run: buyRevoke,
}
addBuyRevokeFlags(cmd)
return cmd
}
func addBuyRevokeFlags(cmd *cobra.Command) {
cmd.Flags().StringP("buy_id", "b", "", "buy id")
cmd.MarkFlagRequired("buy_id")
cmd.Flags().Float64P("fee", "f", 0, "transaction fee")
}
func buyRevoke(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
buyID, _ := cmd.Flags().GetString("buy_id")
fee, _ := cmd.Flags().GetFloat64("fee")
feeInt64 := int64(fee * 1e4)
params := &pty.TradeRevokeBuyTx{
BuyID: buyID,
Fee: feeInt64 * 1e4,
}
ctx := jsonrpc.NewRPCCtx(rpcLaddr, "trade.CreateRawTradeRevokeBuyTx", params, nil)
ctx.RunWithoutMarshal()
}
...@@ -5,46 +5,57 @@ ...@@ -5,46 +5,57 @@
package executor package executor
import ( import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
pty "github.com/33cn/plugin/plugin/dapp/trade/types" pty "github.com/33cn/plugin/plugin/dapp/trade/types"
) )
func (t *trade) ExecDelLocal_SellLimit(sell *pty.TradeForSell, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) ExecDelLocal_SellLimit(sell *pty.TradeForSell, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return t.localDelLog(tx, receipt, index) return t.localDelLog(tx, receipt, index, 0)
} }
func (t *trade) ExecDelLocal_BuyMarket(buy *pty.TradeForBuy, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) ExecDelLocal_BuyMarket(buy *pty.TradeForBuy, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return t.localDelLog(tx, receipt, index) return t.localDelLog(tx, receipt, index, buy.BoardlotCnt)
} }
func (t *trade) ExecDelLocal_RevokeSell(revoke *pty.TradeForRevokeSell, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) ExecDelLocal_RevokeSell(revoke *pty.TradeForRevokeSell, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return t.localDelLog(tx, receipt, index) return t.localDelLog(tx, receipt, index, 0)
} }
func (t *trade) ExecDelLocal_BuyLimit(buy *pty.TradeForBuyLimit, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) ExecDelLocal_BuyLimit(buy *pty.TradeForBuyLimit, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return t.localDelLog(tx, receipt, index) return t.localDelLog(tx, receipt, index, 0)
} }
func (t *trade) ExecDelLocal_SellMarket(sell *pty.TradeForSellMarket, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) ExecDelLocal_SellMarket(sell *pty.TradeForSellMarket, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return t.localDelLog(tx, receipt, index) return t.localDelLog(tx, receipt, index, sell.BoardlotCnt)
} }
func (t *trade) ExecDelLocal_RevokeBuy(revoke *pty.TradeForRevokeBuy, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) ExecDelLocal_RevokeBuy(revoke *pty.TradeForRevokeBuy, tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
return t.localDelLog(tx, receipt, index) return t.localDelLog(tx, receipt, index, 0)
} }
func (t *trade) localDelLog(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) localDelLog(tx *types.Transaction, receipt *types.ReceiptData, index int, tradedBoardlot int64) (*types.LocalDBSet, error) {
var set types.LocalDBSet var set types.LocalDBSet
table := NewOrderTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
for i := 0; i < len(receipt.Logs); i++ { for i := 0; i < len(receipt.Logs); i++ {
item := receipt.Logs[i] item := receipt.Logs[i]
if item.Ty == pty.TyLogTradeSellLimit || item.Ty == pty.TyLogTradeSellRevoke { if item.Ty == pty.TyLogTradeSellLimit {
var receipt pty.ReceiptTradeSellLimit var receipt pty.ReceiptTradeSellLimit
err := types.Decode(item.Log, &receipt) err := types.Decode(item.Log, &receipt)
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.deleteSell([]byte(receipt.Base.SellID), item.Ty) kv := t.deleteSell(receipt.Base, item.Ty, tx, txIndex, table, tradedBoardlot)
set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeSellRevoke {
var receipt pty.ReceiptTradeSellRevoke
err := types.Decode(item.Log, &receipt)
if err != nil {
panic(err) //数据错误了,已经被修改了
}
kv := t.deleteSell(receipt.Base, item.Ty, tx, txIndex, table, tradedBoardlot)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeBuyMarket { } else if item.Ty == pty.TyLogTradeBuyMarket {
var receipt pty.ReceiptTradeBuyMarket var receipt pty.ReceiptTradeBuyMarket
...@@ -52,15 +63,23 @@ func (t *trade) localDelLog(tx *types.Transaction, receipt *types.ReceiptData, i ...@@ -52,15 +63,23 @@ func (t *trade) localDelLog(tx *types.Transaction, receipt *types.ReceiptData, i
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.deleteBuy(receipt.Base) kv := t.deleteBuy(receipt.Base, txIndex, table)
set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeBuyRevoke {
var receipt pty.ReceiptTradeBuyRevoke
err := types.Decode(item.Log, &receipt)
if err != nil {
panic(err) //数据错误了,已经被修改了
}
kv := t.deleteBuyLimit(receipt.Base, item.Ty, tx, txIndex, table, tradedBoardlot)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeBuyRevoke || item.Ty == pty.TyLogTradeBuyLimit { } else if item.Ty == pty.TyLogTradeBuyLimit {
var receipt pty.ReceiptTradeBuyLimit var receipt pty.ReceiptTradeBuyLimit
err := types.Decode(item.Log, &receipt) err := types.Decode(item.Log, &receipt)
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.deleteBuyLimit([]byte(receipt.Base.BuyID), item.Ty) kv := t.deleteBuyLimit(receipt.Base, item.Ty, tx, txIndex, table, tradedBoardlot)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeSellMarket { } else if item.Ty == pty.TyLogTradeSellMarket {
var receipt pty.ReceiptSellMarket var receipt pty.ReceiptSellMarket
...@@ -68,10 +87,19 @@ func (t *trade) localDelLog(tx *types.Transaction, receipt *types.ReceiptData, i ...@@ -68,10 +87,19 @@ func (t *trade) localDelLog(tx *types.Transaction, receipt *types.ReceiptData, i
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.deleteSellMarket(receipt.Base) kv := t.deleteSellMarket(receipt.Base, txIndex, table)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} }
} }
newKvs, err := table.Save()
if err != nil {
tradelog.Error("trade table.Save failed", "error", err)
return nil, err
}
set.KV = append(set.KV, newKvs...)
for _, kv := range set.KV {
t.GetLocalDB().Set(kv.Key, kv.Value)
}
return &set, nil return &set, nil
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package executor package executor
import ( import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
pty "github.com/33cn/plugin/plugin/dapp/trade/types" pty "github.com/33cn/plugin/plugin/dapp/trade/types"
) )
...@@ -35,16 +36,26 @@ func (t *trade) ExecLocal_RevokeBuy(revoke *pty.TradeForRevokeBuy, tx *types.Tra ...@@ -35,16 +36,26 @@ func (t *trade) ExecLocal_RevokeBuy(revoke *pty.TradeForRevokeBuy, tx *types.Tra
func (t *trade) localAddLog(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) { func (t *trade) localAddLog(tx *types.Transaction, receipt *types.ReceiptData, index int) (*types.LocalDBSet, error) {
var set types.LocalDBSet var set types.LocalDBSet
table := NewOrderTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
for i := 0; i < len(receipt.Logs); i++ { for i := 0; i < len(receipt.Logs); i++ {
item := receipt.Logs[i] item := receipt.Logs[i]
if item.Ty == pty.TyLogTradeSellLimit || item.Ty == pty.TyLogTradeSellRevoke { if item.Ty == pty.TyLogTradeSellLimit {
var receipt pty.ReceiptTradeSellLimit var receipt pty.ReceiptTradeSellLimit
err := types.Decode(item.Log, &receipt) err := types.Decode(item.Log, &receipt)
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.saveSell([]byte(receipt.Base.SellID), item.Ty) kv := t.saveSell(receipt.Base, item.Ty, tx, txIndex, table)
set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeSellRevoke {
var receipt pty.ReceiptTradeSellRevoke
err := types.Decode(item.Log, &receipt)
if err != nil {
panic(err) //数据错误了,已经被修改了
}
kv := t.saveSell(receipt.Base, item.Ty, tx, txIndex, table)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeBuyMarket { } else if item.Ty == pty.TyLogTradeBuyMarket {
var receipt pty.ReceiptTradeBuyMarket var receipt pty.ReceiptTradeBuyMarket
...@@ -52,16 +63,25 @@ func (t *trade) localAddLog(tx *types.Transaction, receipt *types.ReceiptData, i ...@@ -52,16 +63,25 @@ func (t *trade) localAddLog(tx *types.Transaction, receipt *types.ReceiptData, i
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.saveBuy(receipt.Base) kv := t.saveBuy(receipt.Base, tx, txIndex, table)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeBuyRevoke || item.Ty == pty.TyLogTradeBuyLimit { } else if item.Ty == pty.TyLogTradeBuyRevoke {
var receipt pty.ReceiptTradeBuyRevoke
err := types.Decode(item.Log, &receipt)
if err != nil {
panic(err) //数据错误了,已经被修改了
}
kv := t.saveBuyLimit(receipt.Base, item.Ty, tx, txIndex, table)
set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeBuyLimit {
var receipt pty.ReceiptTradeBuyLimit var receipt pty.ReceiptTradeBuyLimit
err := types.Decode(item.Log, &receipt) err := types.Decode(item.Log, &receipt)
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.saveBuyLimit([]byte(receipt.Base.BuyID), item.Ty) kv := t.saveBuyLimit(receipt.Base, item.Ty, tx, txIndex, table)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} else if item.Ty == pty.TyLogTradeSellMarket { } else if item.Ty == pty.TyLogTradeSellMarket {
var receipt pty.ReceiptSellMarket var receipt pty.ReceiptSellMarket
...@@ -69,11 +89,20 @@ func (t *trade) localAddLog(tx *types.Transaction, receipt *types.ReceiptData, i ...@@ -69,11 +89,20 @@ func (t *trade) localAddLog(tx *types.Transaction, receipt *types.ReceiptData, i
if err != nil { if err != nil {
panic(err) //数据错误了,已经被修改了 panic(err) //数据错误了,已经被修改了
} }
kv := t.saveSellMarket(receipt.Base) kv := t.saveSellMarket(receipt.Base, tx, txIndex, table)
//tradelog.Info("saveSellMarket", "kv", kv) //tradelog.Info("saveSellMarket", "kv", kv)
set.KV = append(set.KV, kv...) set.KV = append(set.KV, kv...)
} }
} }
newKvs, err := table.Save()
if err != nil {
tradelog.Error("trade table.Save failed", "error", err)
return nil, err
}
set.KV = append(set.KV, newKvs...)
for _, kv := range set.KV {
t.GetLocalDB().Set(kv.Key, kv.Value)
}
return &set, nil return &set, nil
} }
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/33cn/chain33/common/crypto" "github.com/33cn/chain33/common/crypto"
dbm "github.com/33cn/chain33/common/db" dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
pty "github.com/33cn/plugin/plugin/dapp/trade/types" pty "github.com/33cn/plugin/plugin/dapp/trade/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
...@@ -83,9 +84,12 @@ func TestTrade_Exec_SellLimit(t *testing.T) { ...@@ -83,9 +84,12 @@ func TestTrade_Exec_SellLimit(t *testing.T) {
accA, _ := account.NewAccountDB(AssetExecToken, Symbol, stateDB) accA, _ := account.NewAccountDB(AssetExecToken, Symbol, stateDB)
accA.SaveExecAccount(address.ExecAddress("trade"), &accountA) accA.SaveExecAccount(address.ExecAddress("trade"), &accountA)
_, ldb, kvdb := util.CreateTestDB()
driver := newTrade() driver := newTrade()
driver.SetEnv(env.blockHeight, env.blockTime, env.difficulty) driver.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
driver.SetStateDB(stateDB) driver.SetStateDB(stateDB)
driver.SetLocalDB(kvdb)
sell := &pty.TradeSellTx{ sell := &pty.TradeSellTx{
TokenSymbol: Symbol, TokenSymbol: Symbol,
...@@ -124,6 +128,15 @@ func TestTrade_Exec_SellLimit(t *testing.T) { ...@@ -124,6 +128,15 @@ func TestTrade_Exec_SellLimit(t *testing.T) {
assert.Equal(t, int64(0), sellOrder.SoldBoardlot) assert.Equal(t, int64(0), sellOrder.SoldBoardlot)
assert.Equal(t, string(Nodes[0]), sellOrder.Address) assert.Equal(t, string(Nodes[0]), sellOrder.Address)
receiptDataSell := &types.ReceiptData{
Ty: receipt.Ty,
Logs: receipt.Logs,
}
_, err = driver.ExecLocal(tx, receiptDataSell, env.index)
assert.Nil(t, err)
kvdb.Commit()
// test buy market
buy := &pty.TradeBuyTx{ buy := &pty.TradeBuyTx{
SellID: sellOrder.SellID, SellID: sellOrder.SellID,
BoardlotCnt: buyArgs.total, BoardlotCnt: buyArgs.total,
...@@ -157,6 +170,27 @@ func TestTrade_Exec_SellLimit(t *testing.T) { ...@@ -157,6 +170,27 @@ func TestTrade_Exec_SellLimit(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expect.total, sellOrder.TotalBoardlot-sellOrder.SoldBoardlot) assert.Equal(t, expect.total, sellOrder.TotalBoardlot-sellOrder.SoldBoardlot)
receiptDataBuy := &types.ReceiptData{
Ty: receipt.Ty,
Logs: receipt.Logs,
}
_, err = driver.ExecLocal(tx, receiptDataBuy, env.index)
assert.Nil(t, err)
req := &pty.ReqAddrAssets{
Addr: string(Nodes[0]),
Status: pty.TradeOrderStatusOnSale,
Token: nil,
Direction: 1,
Count: 10,
FromKey: "",
}
resp, err := driver.Query("GetOnesOrderWithStatus", types.Encode(req))
assert.Nil(t, err)
orders, ok := resp.(*pty.ReplyTradeOrders)
assert.True(t, ok)
assert.Equal(t, 1, len(orders.Orders))
ldb.Close()
} }
func TestTrade_Exec_BuyLimit(t *testing.T) { func TestTrade_Exec_BuyLimit(t *testing.T) {
...@@ -185,6 +219,8 @@ func TestTrade_Exec_BuyLimit(t *testing.T) { ...@@ -185,6 +219,8 @@ func TestTrade_Exec_BuyLimit(t *testing.T) {
} }
stateDB, _ := dbm.NewGoMemDB("1", "2", 100) stateDB, _ := dbm.NewGoMemDB("1", "2", 100)
_, ldb, kvdb := util.CreateTestDB()
accB := account.NewCoinsAccount() accB := account.NewCoinsAccount()
accB.SetDB(stateDB) accB.SetDB(stateDB)
accB.SaveExecAccount(address.ExecAddress("trade"), &accountB) accB.SaveExecAccount(address.ExecAddress("trade"), &accountB)
...@@ -195,6 +231,7 @@ func TestTrade_Exec_BuyLimit(t *testing.T) { ...@@ -195,6 +231,7 @@ func TestTrade_Exec_BuyLimit(t *testing.T) {
driver := newTrade() driver := newTrade()
driver.SetEnv(env.blockHeight, env.blockTime, env.difficulty) driver.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
driver.SetStateDB(stateDB) driver.SetStateDB(stateDB)
driver.SetLocalDB(kvdb)
buy := &pty.TradeBuyLimitTx{ buy := &pty.TradeBuyLimitTx{
TokenSymbol: Symbol, TokenSymbol: Symbol,
...@@ -233,6 +270,13 @@ func TestTrade_Exec_BuyLimit(t *testing.T) { ...@@ -233,6 +270,13 @@ func TestTrade_Exec_BuyLimit(t *testing.T) {
assert.Equal(t, int64(0), buyLimitOrder.BoughtBoardlot) assert.Equal(t, int64(0), buyLimitOrder.BoughtBoardlot)
assert.Equal(t, string(Nodes[1]), buyLimitOrder.Address) assert.Equal(t, string(Nodes[1]), buyLimitOrder.Address)
receiptDataBuy := &types.ReceiptData{
Ty: receipt.Ty,
Logs: receipt.Logs,
}
_, err = driver.ExecLocal(tx, receiptDataBuy, env.index)
assert.Nil(t, err)
sell := &pty.TradeSellMarketTx{ sell := &pty.TradeSellMarketTx{
BuyID: buyLimitOrder.BuyID, BuyID: buyLimitOrder.BuyID,
BoardlotCnt: sellArgs.total, BoardlotCnt: sellArgs.total,
...@@ -266,6 +310,15 @@ func TestTrade_Exec_BuyLimit(t *testing.T) { ...@@ -266,6 +310,15 @@ func TestTrade_Exec_BuyLimit(t *testing.T) {
err = types.Decode(receipt.KV[4].Value, &buyLimitOrder) err = types.Decode(receipt.KV[4].Value, &buyLimitOrder)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, expect.total, buyLimitOrder.TotalBoardlot-buyLimitOrder.BoughtBoardlot) assert.Equal(t, expect.total, buyLimitOrder.TotalBoardlot-buyLimitOrder.BoughtBoardlot)
receiptDataSell := &types.ReceiptData{
Ty: receipt.Ty,
Logs: receipt.Logs,
}
_, err = driver.ExecLocal(tx, receiptDataSell, env.index)
assert.Nil(t, err)
ldb.Close()
} }
func signTx(tx *types.Transaction, hexPrivKey string) (*types.Transaction, error) { func signTx(tx *types.Transaction, hexPrivKey string) (*types.Transaction, error) {
......
// 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 (
"encoding/hex"
"fmt"
"strconv"
"github.com/33cn/chain33/common"
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
pty "github.com/33cn/plugin/plugin/dapp/trade/types"
)
/*
现有接口
1. 查询地址对应的买单 (无分页)
1.1 只指定地址 -> owner
1.2 同时指定地址和token -> owner_asset
1.3 显示一个用户成交的所有买单 -> owner
1.4 显示一个用户成交的指定一个或者多个token所有买单 -> owner_asset 不支持多个
2. 分状态查询地址的买单: 状态 地址 (无分页) -> owner_status
3. 显示一个token 指定数量的买单 GetTokenBuyOrderByStatus -> asset_inBuy_status
4. 显示指定token出售者的一个或多个token 或 不指定token 的卖单 (无分页) -> owner_asset/owner_asset_isSell 不支持多个
5. 显示指定状态下的某地址卖单 (无分页) -> owner_isSell_status
6. 显示一个token 指定数量的卖单 -> asset_isSell
7. 根据状态分页列出某地址的订单(包括买单卖单) owner_status
*/
var opt_order_table = &table.Option{
Prefix: "LODB-trade",
Name: "order",
Primary: "txIndex",
// asset = asset_exec+asset_symbol
//
// status: 设计为可以同时查询几种的并集 , 存储为前缀, 需要提前设计需要合并的, 用前缀表示
// 进行中, 撤销, 部分成交 , 全部成交, 完成状态统一前缀. 数字和原来不一样
// 00 10 11 12 1*
// 排序过滤条件: 可以组合,status&isSell 和前缀冲突
Index: []string{
"key", // 内部查询用
"asset", // 按资产统计订单
"asset_isSell_status", // 接口 3
// "asset_status", 可能需求, 用于资产的交易历史
// "asset_isSell",
"owner", // 接口 1.1, 1.3
"owner_asset", // 接口 1.2, 1.4, 4, 7
"owner_asset_isSell", // 接口 4
"owner_asset_status", // 新需求, 在
"owner_isSell", // 接口 6
// "owner_isSell_status", 可能需求, 界面分开显示订单
// "owner_isSell_statusPrefix", // 状态可以定制组合, 成交历史需求
"owner_status", // 接口 2
"assset_isSell_isFinished", // 用 isFinish, 进行订单是否完成的列表功能
"owner_asset_isFinished",
"owner_isFinished",
// "owner_statusPrefix", // 状态可以定制组合 , 成交历史需求
},
}
// OrderRow order row
type OrderRow struct {
*pty.LocalOrder
}
// NewOrderRow create row
func NewOrderRow() *OrderRow {
return &OrderRow{LocalOrder: nil}
}
// CreateRow create row
func (r *OrderRow) CreateRow() *table.Row {
return &table.Row{Data: &pty.LocalOrder{}}
}
// SetPayload set payload
func (r *OrderRow) SetPayload(data types.Message) error {
if d, ok := data.(*pty.LocalOrder); ok {
r.LocalOrder = d
return nil
}
return types.ErrTypeAsset
}
// Get get index key
func (r *OrderRow) Get(key string) ([]byte, error) {
switch key {
case "txIndex":
return []byte(r.TxIndex), nil
case "key":
return []byte(r.Key), nil
case "asset":
return []byte(r.asset()), nil
case "asset_isSell_status":
return []byte(fmt.Sprintf("%s_%d_%s", r.asset(), r.isSell(), r.status())), nil
case "owner":
return []byte(r.Owner), nil
case "owner_asset":
return []byte(fmt.Sprintf("%s_%s", r.Owner, r.asset())), nil
case "owner_asset_isSell":
return []byte(fmt.Sprintf("%s_%s_%d", r.Owner, r.asset(), r.isSell())), nil
case "owner_asset_status":
return []byte(fmt.Sprintf("%s_%s_%s", r.Owner, r.asset(), r.status())), nil
case "owner_isSell":
return []byte(fmt.Sprintf("%s_%d", r.Owner, r.isSell())), nil
//case "owner_isSell_statusPrefix":
// return []byte(fmt.Sprintf("%s_%d_%s", r.Owner, r.asset(), r.isSell())), nil
case "owner_status":
return []byte(fmt.Sprintf("%s_%s", r.Owner, r.status())), nil
//case "owner_statusPrefix":
// return []byte(fmt.Sprintf("%s_%d", r.Owner, r.isSell())), nil
case "assset_isSell_isFinished":
return []byte(fmt.Sprintf("%s_%d_%d", r.Owner, r.isSell(), r.isFinished())), nil
case "owner_asset_isFinished":
return []byte(fmt.Sprintf("%s_%s_%d", r.Owner, r.asset(), r.isFinished())), nil
case "owner_isFinished":
return []byte(fmt.Sprintf("%s_%d", r.Owner, r.isFinished())), nil
default:
return nil, types.ErrNotFound
}
}
func (r *OrderRow) asset() string {
return r.LocalOrder.AssetExec + "." + r.LocalOrder.AssetSymbol
}
func (r *OrderRow) isSell() int {
if r.IsSellOrder {
return 1
}
return 0
}
func (r *OrderRow) isFinished() int {
if r.IsFinished {
return 1
}
return 0
}
// status: 设计为可以同时查询几种的并集 , 存储为前缀, 需要提前设计需要合并的, 用前缀表示
// 进行中, 撤销, 部分成交 , 全部成交, 完成状态统一前缀. 数字和原来不一样
// 01 10 11 12 19 -> 1*
func (r *OrderRow) status() string {
if r.Status == pty.TradeOrderStatusOnBuy || r.Status == pty.TradeOrderStatusOnSale {
return "01" // 试图用1 可以匹配所有完成的
} else if r.Status == pty.TradeOrderStatusSoldOut || r.Status == pty.TradeOrderStatusBoughtOut {
return "12"
} else if r.Status == pty.TradeOrderStatusRevoked || r.Status == pty.TradeOrderStatusBuyRevoked {
return "10"
} else if r.Status == pty.TradeOrderStatusSellHalfRevoked || r.Status == pty.TradeOrderStatusBuyHalfRevoked {
return "11"
} else if r.Status == pty.TradeOrderStatusGroupComplete {
return "1" // 1* match complete
}
return "XX"
}
// NewOrderTable create order table
func NewOrderTable(kvdb dbm.KV) *table.Table {
rowMeta := NewOrderRow()
rowMeta.SetPayload(&pty.LocalOrder{})
t, err := table.NewTable(rowMeta, kvdb, opt_order_table)
if err != nil {
panic(err)
}
return t
}
func (t *trade) genSellLimit(tx *types.Transaction, sell *pty.ReceiptSellBase,
sellorder *pty.SellOrder, txIndex string) *pty.LocalOrder {
order := &pty.LocalOrder{
AssetSymbol: sellorder.TokenSymbol,
TxIndex: txIndex,
Owner: sellorder.Address,
AmountPerBoardlot: sellorder.AmountPerBoardlot,
MinBoardlot: sellorder.MinBoardlot,
PricePerBoardlot: sellorder.PricePerBoardlot,
TotalBoardlot: sellorder.TotalBoardlot,
TradedBoardlot: sellorder.SoldBoardlot,
BuyID: "",
Status: sellorder.Status,
SellID: sell.SellID,
TxHash: []string{common.ToHex(tx.Hash())},
Height: sell.Height,
Key: sell.SellID,
BlockTime: t.GetBlockTime(),
IsSellOrder: true,
AssetExec: sellorder.AssetExec,
IsFinished: false,
}
return order
}
func (t *trade) updateSellLimit(tx *types.Transaction, sell *pty.ReceiptSellBase,
sellorder *pty.SellOrder, txIndex string, ldb *table.Table) *pty.LocalOrder {
xs, err := ldb.ListIndex("key", []byte(sell.SellID), nil, 1, 0)
if err != nil || len(xs) != 1 {
return nil
}
order, ok := xs[0].Data.(*pty.LocalOrder)
tradelog.Debug("Table dbg", "sell-update", order, "data", xs[0].Data)
if !ok {
tradelog.Error("Table failed", "sell-update", order)
return nil
}
status := sellorder.Status
if status == pty.TradeOrderStatusRevoked && sell.SoldBoardlot > 0 {
status = pty.TradeOrderStatusSellHalfRevoked
}
order.Status = status
order.TxHash = append(order.TxHash, common.ToHex(tx.Hash()))
order.TradedBoardlot = sellorder.SoldBoardlot
order.IsFinished = (status != pty.TradeOrderStatusOnSale)
tradelog.Debug("Table", "sell-update", order)
ldb.Replace(order)
return order
}
func (t *trade) rollBackSellLimit(tx *types.Transaction, sell *pty.ReceiptSellBase,
sellorder *pty.SellOrder, txIndex string, ldb *table.Table, tradedBoardlot int64) *pty.LocalOrder {
xs, err := ldb.ListIndex("key", []byte(sell.SellID), nil, 1, 0)
if err != nil || len(xs) != 1 {
return nil
}
order, ok := xs[0].Data.(*pty.LocalOrder)
if !ok {
return nil
}
// 撤销订单回滚, 只需要修改状态
// 其他的操作需要还修改数量
order.Status = pty.TradeOrderStatusOnSale
order.TxHash = order.TxHash[:len(order.TxHash)-1]
order.TradedBoardlot = order.TradedBoardlot - tradedBoardlot
order.IsFinished = (order.Status != pty.TradeOrderStatusOnSale)
ldb.Replace(order)
return order
}
func parseOrderAmountFloat(s string) int64 {
x, err := strconv.ParseFloat(s, 64)
if err != nil {
tradelog.Error("parseOrderAmountFloat", "decode receipt", err)
return 0
}
return int64(x * float64(types.TokenPrecision))
}
func parseOrderPriceFloat(s string) int64 {
x, err := strconv.ParseFloat(s, 64)
if err != nil {
tradelog.Error("parseOrderPriceFloat", "decode receipt", err)
return 0
}
return int64(x * float64(types.Coin))
}
func (t *trade) genSellMarket(tx *types.Transaction, sell *pty.ReceiptSellBase, txIndex string) *pty.LocalOrder {
order := &pty.LocalOrder{
AssetSymbol: sell.TokenSymbol,
TxIndex: txIndex,
Owner: sell.Owner,
AmountPerBoardlot: parseOrderAmountFloat(sell.AmountPerBoardlot),
MinBoardlot: sell.MinBoardlot,
PricePerBoardlot: parseOrderPriceFloat(sell.PricePerBoardlot),
TotalBoardlot: sell.TotalBoardlot,
TradedBoardlot: sell.SoldBoardlot,
BuyID: sell.BuyID,
Status: pty.TradeOrderStatusSoldOut,
SellID: calcTokenSellID(hex.EncodeToString(tx.Hash())),
TxHash: []string{common.ToHex(tx.Hash())},
Height: sell.Height,
Key: calcTokenSellID(hex.EncodeToString(tx.Hash())),
BlockTime: t.GetBlockTime(),
IsSellOrder: true,
AssetExec: sell.AssetExec,
IsFinished: true,
}
return order
}
func (t *trade) genBuyLimit(tx *types.Transaction, buy *pty.ReceiptBuyBase, txIndex string) *pty.LocalOrder {
order := &pty.LocalOrder{
AssetSymbol: buy.TokenSymbol,
TxIndex: txIndex,
Owner: buy.Owner,
AmountPerBoardlot: parseOrderAmountFloat(buy.AmountPerBoardlot),
MinBoardlot: buy.MinBoardlot,
PricePerBoardlot: parseOrderPriceFloat(buy.PricePerBoardlot),
TotalBoardlot: buy.TotalBoardlot,
TradedBoardlot: buy.BoughtBoardlot,
BuyID: buy.BuyID,
Status: pty.TradeOrderStatusOnBuy,
SellID: "",
TxHash: []string{common.ToHex(tx.Hash())},
Height: buy.Height,
Key: buy.BuyID,
BlockTime: t.GetBlockTime(),
IsSellOrder: false,
AssetExec: buy.AssetExec,
IsFinished: false,
}
return order
}
func (t *trade) updateBuyLimit(tx *types.Transaction, buy *pty.ReceiptBuyBase,
buyorder *pty.BuyLimitOrder, txIndex string, ldb *table.Table) *pty.LocalOrder {
xs, err := ldb.ListIndex("key", []byte(buy.BuyID), nil, 1, 0)
if err != nil || len(xs) != 1 {
return nil
}
order, ok := xs[0].Data.(*pty.LocalOrder)
if !ok {
return nil
}
status := buyorder.Status
if status == pty.TradeOrderStatusBuyRevoked && buy.BoughtBoardlot > 0 {
status = pty.TradeOrderStatusBuyHalfRevoked
}
order.Status = status
order.TxHash = append(order.TxHash, common.ToHex(tx.Hash()))
order.TradedBoardlot = buyorder.BoughtBoardlot
order.IsFinished = (status != pty.TradeOrderStatusOnBuy)
ldb.Replace(order)
return order
}
func (t *trade) rollbackBuyLimit(tx *types.Transaction, buy *pty.ReceiptBuyBase,
buyorder *pty.BuyLimitOrder, txIndex string, ldb *table.Table, traded int64) *pty.LocalOrder {
xs, err := ldb.ListIndex("key", []byte(buy.BuyID), nil, 1, 0)
if err != nil || len(xs) != 1 {
return nil
}
order, ok := xs[0].Data.(*pty.LocalOrder)
if !ok {
return nil
}
order.Status = pty.TradeOrderStatusOnBuy
order.TxHash = order.TxHash[:len(order.TxHash)-1]
order.TradedBoardlot = order.TradedBoardlot - traded
order.IsFinished = false
ldb.Replace(order)
return order
}
func (t *trade) genBuyMarket(tx *types.Transaction, buy *pty.ReceiptBuyBase, txIndex string) *pty.LocalOrder {
order := &pty.LocalOrder{
AssetSymbol: buy.TokenSymbol,
TxIndex: txIndex,
Owner: buy.Owner,
AmountPerBoardlot: parseOrderAmountFloat(buy.AmountPerBoardlot),
MinBoardlot: buy.MinBoardlot,
PricePerBoardlot: parseOrderPriceFloat(buy.PricePerBoardlot),
TotalBoardlot: buy.TotalBoardlot,
TradedBoardlot: buy.BoughtBoardlot,
BuyID: calcTokenBuyID(hex.EncodeToString(tx.Hash())),
Status: pty.TradeOrderStatusBoughtOut,
SellID: buy.SellID,
TxHash: []string{common.ToHex(tx.Hash())},
Height: buy.Height,
Key: calcTokenBuyID(hex.EncodeToString(tx.Hash())),
BlockTime: t.GetBlockTime(),
IsSellOrder: true,
AssetExec: buy.AssetExec,
IsFinished: true,
}
return order
}
func list(db dbm.KVDB, indexName string, data *pty.LocalOrder, count, direction int32) ([]*table.Row, error) {
query := NewOrderTable(db).GetQuery(db)
var primary []byte
if len(data.TxIndex) > 0 {
primary = []byte(data.TxIndex)
}
cur := &OrderRow{LocalOrder: data}
index, err := cur.Get(indexName)
if err != nil {
tradelog.Error("query List failed", "key", string(primary), "param", data, "err", err)
return nil, err
}
tradelog.Debug("query List dbg", "indexName", indexName, "index", string(index), "primary", primary, "count", count, "direction", direction)
rows, err := query.ListIndex(indexName, index, primary, count, direction)
if err != nil {
tradelog.Error("query List failed", "key", string(primary), "param", data, "err", err)
return nil, err
}
if len(rows) == 0 {
return nil, types.ErrNotFound
}
return rows, nil
}
/*
按 资产 查询 :
按 资产 & 地址 查询
按 地址
排序和分类
1. 时间顺序 txindex
1. 分类, 不同的状态 & 不同的性质: 买/卖
交易 -> 订单 按订单来 (交易和订单是多对多的关系,不适合joinTable)
交易 T1 Create -> T2 part-take -> T3 Revoke
订单左为进行中, 右为完成,
订单 (C1) | () -> (C1m) | (C2) -> () | (C2, C1r)
查询交易 / 查询订单
C -> C/M -> C/D
\
\ ->R
状态 1, TradeOrderStatusOnSale, 在售
状态 2: TradeOrderStatusSoldOut,售完
状态 3: TradeOrderStatusRevoked, 卖单被撤回
状态 4: TradeOrderStatusExpired, 订单超时(目前不支持订单超时)
状态 5: TradeOrderStatusOnBuy, 求购
状态 6: TradeOrderStatusBoughtOut, 购买完成
状态 7: TradeOrderStatusBuyRevoked, 买单被撤回
*/
package executor
import (
"testing"
"github.com/33cn/chain33/system/dapp"
pty "github.com/33cn/plugin/plugin/dapp/trade/types"
//"github.com/33cn/chain33/common/db"
//"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/util"
"github.com/stretchr/testify/assert"
)
var order1 = &pty.LocalOrder{
AssetSymbol: "A",
Owner: "O1",
AmountPerBoardlot: 1,
MinBoardlot: 1,
PricePerBoardlot: 1,
TotalBoardlot: 10,
TradedBoardlot: 0,
BuyID: "B1",
Status: pty.TradeOrderStatusOnBuy,
SellID: "",
TxHash: nil,
Height: 1,
Key: "B1",
BlockTime: 1,
IsSellOrder: false,
AssetExec: "a",
TxIndex: dapp.HeightIndexStr(1, 1),
IsFinished: false,
}
var order2 = &pty.LocalOrder{
AssetSymbol: "A",
Owner: "O1",
AmountPerBoardlot: 1,
MinBoardlot: 1,
PricePerBoardlot: 1,
TotalBoardlot: 10,
TradedBoardlot: 0,
BuyID: "B2",
Status: pty.TradeOrderStatusOnBuy,
SellID: "",
TxHash: nil,
Height: 2,
Key: "B2",
BlockTime: 2,
IsSellOrder: false,
AssetExec: "a",
TxIndex: dapp.HeightIndexStr(2, 1),
IsFinished: false,
}
func TestListAll(t *testing.T) {
dir, ldb, tdb := util.CreateTestDB()
t.Log(dir, ldb, tdb)
odb := NewOrderTable(tdb)
odb.Add(order1)
odb.Add(order2)
kvs, err := odb.Save()
assert.Nil(t, err)
t.Log(kvs)
ldb.Close()
}
...@@ -60,6 +60,10 @@ func (t *trade) Query_GetOnesOrderWithStatus(req *pty.ReqAddrAssets) (types.Mess ...@@ -60,6 +60,10 @@ func (t *trade) Query_GetOnesOrderWithStatus(req *pty.ReqAddrAssets) (types.Mess
return t.GetOnesOrderWithStatus(req) return t.GetOnesOrderWithStatus(req)
} }
func (t *trade) Query_GetOneOrder(req *pty.ReqAddrAssets) (types.Message, error) {
return t.GetOneOrder(req)
}
func (t *trade) GetOnesSellOrder(addrTokens *pty.ReqAddrAssets) (types.Message, error) { func (t *trade) GetOnesSellOrder(addrTokens *pty.ReqAddrAssets) (types.Message, error) {
var keys [][]byte var keys [][]byte
if 0 == len(addrTokens.Token) { if 0 == len(addrTokens.Token) {
...@@ -641,7 +645,7 @@ func (t *trade) loadOrderFromKey(key []byte) *pty.ReplyTradeOrder { ...@@ -641,7 +645,7 @@ func (t *trade) loadOrderFromKey(key []byte) *pty.ReplyTradeOrder {
return txResult2OrderReply(txResult) return txResult2OrderReply(txResult)
} }
func (t *trade) GetOnesOrderWithStatus(req *pty.ReqAddrAssets) (types.Message, error) { func (t *trade) GetOnesOrderWithStatusV1(req *pty.ReqAddrAssets) (types.Message, error) {
fromKey := []byte("") fromKey := []byte("")
if len(req.FromKey) != 0 { if len(req.FromKey) != 0 {
order := t.loadOrderFromKey([]byte(req.FromKey)) order := t.loadOrderFromKey([]byte(req.FromKey))
...@@ -674,3 +678,78 @@ func (t *trade) GetOnesOrderWithStatus(req *pty.ReqAddrAssets) (types.Message, e ...@@ -674,3 +678,78 @@ func (t *trade) GetOnesOrderWithStatus(req *pty.ReqAddrAssets) (types.Message, e
} }
return &replys, nil return &replys, nil
} }
func (t *trade) GetOnesOrderWithStatus(req *pty.ReqAddrAssets) (types.Message, error) {
orderStatus, orderType := fromStatus(req.Status)
if orderStatus == orderStatusInvalid || orderType == orderTypeInvalid {
return nil, types.ErrInvalidParam
}
// 使用 owner isFinished 组合
var order pty.LocalOrder
if orderStatus == orderStatusOn {
order.IsFinished = false
} else {
order.IsFinished = true
}
order.Owner = req.Addr
if len(req.FromKey) > 0 {
order.TxIndex = req.FromKey
}
rows, err := list(t.GetLocalDB(), "owner_isFinished", &order, req.Count, req.Direction)
if err != nil {
tradelog.Error("GetOnesOrderWithStatus", "err", err)
return nil, err
}
var replys pty.ReplyTradeOrders
for _, row := range rows {
o, ok := row.Data.(*pty.LocalOrder)
if !ok {
tradelog.Error("GetOnesOrderWithStatus", "err", "bad row type")
return nil, types.ErrTypeAsset
}
reply := fmtReply(o)
replys.Orders = append(replys.Orders, reply)
}
return &replys, nil
}
func fmtReply(order *pty.LocalOrder) *pty.ReplyTradeOrder {
return &pty.ReplyTradeOrder{
TokenSymbol: order.AssetSymbol,
Owner: order.Owner,
AmountPerBoardlot: order.AmountPerBoardlot,
MinBoardlot: order.MinBoardlot,
PricePerBoardlot: order.PricePerBoardlot,
TotalBoardlot: order.TotalBoardlot,
TradedBoardlot: order.TradedBoardlot,
BuyID: order.BuyID,
Status: order.Status,
SellID: order.SellID,
TxHash: order.TxHash[0],
Height: order.Height,
Key: order.TxIndex,
BlockTime: order.BlockTime,
IsSellOrder: order.IsSellOrder,
AssetExec: order.AssetExec,
}
}
func (t *trade) GetOneOrder(req *pty.ReqAddrAssets) (types.Message, error) {
query := NewOrderTable(t.GetLocalDB())
tradelog.Debug("query GetData dbg", "primary", req.FromKey)
row, err := query.GetData([]byte(req.FromKey))
if err != nil {
tradelog.Error("query GetData failed", "key", req.FromKey, "err", err)
return nil, err
}
o, ok := row.Data.(*pty.LocalOrder)
if !ok {
tradelog.Error("query GetData failed", "err", "bad row type")
return nil, types.ErrTypeAsset
}
reply := fmtReply(o)
return reply, nil
}
...@@ -19,6 +19,7 @@ trade执行器支持trade的创建和交易, ...@@ -19,6 +19,7 @@ trade执行器支持trade的创建和交易,
import ( import (
log "github.com/33cn/chain33/common/log/log15" log "github.com/33cn/chain33/common/log/log15"
"github.com/33cn/chain33/common/db/table"
drivers "github.com/33cn/chain33/system/dapp" drivers "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
pty "github.com/33cn/plugin/plugin/dapp/trade/types" pty "github.com/33cn/plugin/plugin/dapp/trade/types"
...@@ -81,8 +82,16 @@ func genSaveSellKv(sellorder *pty.SellOrder) []*types.KeyValue { ...@@ -81,8 +82,16 @@ func genSaveSellKv(sellorder *pty.SellOrder) []*types.KeyValue {
return kv return kv
} }
func (t *trade) saveSell(sellID []byte, ty int32) []*types.KeyValue { func (t *trade) saveSell(base *pty.ReceiptSellBase, ty int32, tx *types.Transaction, txIndex string, ldb *table.Table) []*types.KeyValue {
sellorder := t.getSellOrderFromDb(sellID) sellorder := t.getSellOrderFromDb([]byte(base.SellID))
if ty == pty.TyLogTradeSellLimit && sellorder.SoldBoardlot == 0 {
newOrder := t.genSellLimit(tx, base, sellorder, txIndex)
tradelog.Info("Table", "sell-add", newOrder)
ldb.Add(newOrder)
} else {
t.updateSellLimit(tx, base, sellorder, txIndex, ldb)
}
return genSaveSellKv(sellorder) return genSaveSellKv(sellorder)
} }
...@@ -106,20 +115,29 @@ func genDeleteSellKv(sellorder *pty.SellOrder) []*types.KeyValue { ...@@ -106,20 +115,29 @@ func genDeleteSellKv(sellorder *pty.SellOrder) []*types.KeyValue {
return kv return kv
} }
func (t *trade) deleteSell(sellID []byte, ty int32) []*types.KeyValue { func (t *trade) deleteSell(base *pty.ReceiptSellBase, ty int32, tx *types.Transaction, txIndex string, ldb *table.Table, tradedBoardlot int64) []*types.KeyValue {
sellorder := t.getSellOrderFromDb(sellID) sellorder := t.getSellOrderFromDb([]byte(base.SellID))
if ty == pty.TyLogTradeSellLimit && sellorder.SoldBoardlot == 0 {
ldb.Del([]byte(txIndex))
} else {
t.rollBackSellLimit(tx, base, sellorder, txIndex, ldb, tradedBoardlot)
}
return genDeleteSellKv(sellorder) return genDeleteSellKv(sellorder)
} }
func (t *trade) saveBuy(receiptTradeBuy *pty.ReceiptBuyBase) []*types.KeyValue { func (t *trade) saveBuy(receiptTradeBuy *pty.ReceiptBuyBase, tx *types.Transaction, txIndex string, ldb *table.Table) []*types.KeyValue {
//tradelog.Info("save", "buy", receiptTradeBuy) //tradelog.Info("save", "buy", receiptTradeBuy)
var kv []*types.KeyValue var kv []*types.KeyValue
order := t.genBuyMarket(tx, receiptTradeBuy, txIndex)
tradelog.Debug("trade BuyMarket save local", "order", order)
ldb.Add(order)
return saveBuyMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusBoughtOut, t.GetHeight()) return saveBuyMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusBoughtOut, t.GetHeight())
} }
func (t *trade) deleteBuy(receiptTradeBuy *pty.ReceiptBuyBase) []*types.KeyValue { func (t *trade) deleteBuy(receiptTradeBuy *pty.ReceiptBuyBase, txIndex string, ldb *table.Table) []*types.KeyValue {
var kv []*types.KeyValue var kv []*types.KeyValue
ldb.Del([]byte(txIndex))
return deleteBuyMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusBoughtOut, t.GetHeight()) return deleteBuyMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusBoughtOut, t.GetHeight())
} }
...@@ -145,8 +163,17 @@ func genSaveBuyLimitKv(buyOrder *pty.BuyLimitOrder) []*types.KeyValue { ...@@ -145,8 +163,17 @@ func genSaveBuyLimitKv(buyOrder *pty.BuyLimitOrder) []*types.KeyValue {
return kv return kv
} }
func (t *trade) saveBuyLimit(buyID []byte, ty int32) []*types.KeyValue { func (t *trade) saveBuyLimit(buy *pty.ReceiptBuyBase, ty int32, tx *types.Transaction, txIndex string, ldb *table.Table) []*types.KeyValue {
buyOrder := t.getBuyOrderFromDb(buyID) buyOrder := t.getBuyOrderFromDb([]byte(buy.BuyID))
tradelog.Debug("Table", "buy-add", buyOrder)
if buyOrder.Status == pty.TradeOrderStatusOnBuy && buy.BoughtBoardlot == 0 {
order := t.genBuyLimit(tx, buy, txIndex)
tradelog.Info("Table", "buy-add", order)
ldb.Add(order)
} else {
t.updateBuyLimit(tx, buy, buyOrder, txIndex, ldb)
}
return genSaveBuyLimitKv(buyOrder) return genSaveBuyLimitKv(buyOrder)
} }
...@@ -170,18 +197,26 @@ func genDeleteBuyLimitKv(buyOrder *pty.BuyLimitOrder) []*types.KeyValue { ...@@ -170,18 +197,26 @@ func genDeleteBuyLimitKv(buyOrder *pty.BuyLimitOrder) []*types.KeyValue {
return kv return kv
} }
func (t *trade) deleteBuyLimit(buyID []byte, ty int32) []*types.KeyValue { func (t *trade) deleteBuyLimit(buy *pty.ReceiptBuyBase, ty int32, tx *types.Transaction, txIndex string, ldb *table.Table, traded int64) []*types.KeyValue {
buyOrder := t.getBuyOrderFromDb(buyID) buyOrder := t.getBuyOrderFromDb([]byte(buy.BuyID))
if ty == pty.TyLogTradeBuyLimit && buy.BoughtBoardlot == 0 {
ldb.Del([]byte(txIndex))
} else {
t.rollbackBuyLimit(tx, buy, buyOrder, txIndex, ldb, traded)
}
return genDeleteBuyLimitKv(buyOrder) return genDeleteBuyLimitKv(buyOrder)
} }
func (t *trade) saveSellMarket(receiptTradeBuy *pty.ReceiptSellBase) []*types.KeyValue { func (t *trade) saveSellMarket(receiptTradeBuy *pty.ReceiptSellBase, tx *types.Transaction, txIndex string, ldb *table.Table) []*types.KeyValue {
var kv []*types.KeyValue var kv []*types.KeyValue
order := t.genSellMarket(tx, receiptTradeBuy, txIndex)
ldb.Add(order)
return saveSellMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusSoldOut, t.GetHeight()) return saveSellMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusSoldOut, t.GetHeight())
} }
func (t *trade) deleteSellMarket(receiptTradeBuy *pty.ReceiptSellBase) []*types.KeyValue { func (t *trade) deleteSellMarket(receiptTradeBuy *pty.ReceiptSellBase, txIndex string, ldb *table.Table) []*types.KeyValue {
var kv []*types.KeyValue var kv []*types.KeyValue
ldb.Del([]byte(txIndex))
return deleteSellMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusSoldOut, t.GetHeight()) return deleteSellMarketOrderKeyValue(kv, receiptTradeBuy, pty.TradeOrderStatusSoldOut, t.GetHeight())
} }
......
...@@ -284,6 +284,27 @@ message ReqBuyToken { ...@@ -284,6 +284,27 @@ message ReqBuyToken {
string buyer = 2; string buyer = 2;
} }
message LocalOrder {
string assetSymbol = 1;
string owner = 2;
int64 amountPerBoardlot = 3;
int64 minBoardlot = 4;
int64 pricePerBoardlot = 5;
int64 totalBoardlot = 6;
int64 tradedBoardlot = 7;
string buyID = 8;
int32 status = 9;
string sellID = 10;
repeated string txHash = 11;
int64 height = 12;
string key = 13;
int64 blockTime = 14;
bool isSellOrder = 15;
string assetExec = 16;
string txIndex = 17;
bool isFinished = 18;
}
service trade { service trade {
rpc CreateRawTradeSellTx(TradeForSell) returns (UnsignTx) {} rpc CreateRawTradeSellTx(TradeForSell) returns (UnsignTx) {}
rpc CreateRawTradeBuyTx(TradeForBuy) returns (UnsignTx) {} rpc CreateRawTradeBuyTx(TradeForBuy) returns (UnsignTx) {}
......
...@@ -35,6 +35,9 @@ const ( ...@@ -35,6 +35,9 @@ const (
TradeOrderStatusOnBuy TradeOrderStatusOnBuy
TradeOrderStatusBoughtOut TradeOrderStatusBoughtOut
TradeOrderStatusBuyRevoked TradeOrderStatusBuyRevoked
TradeOrderStatusSellHalfRevoked
TradeOrderStatusBuyHalfRevoked
TradeOrderStatusGroupComplete
) )
//SellOrderStatus : sell order status map //SellOrderStatus : sell order status map
......
...@@ -2410,6 +2410,181 @@ func (m *ReqBuyToken) GetBuyer() string { ...@@ -2410,6 +2410,181 @@ func (m *ReqBuyToken) GetBuyer() string {
return "" return ""
} }
type LocalOrder struct {
AssetSymbol string `protobuf:"bytes,1,opt,name=assetSymbol,proto3" json:"assetSymbol,omitempty"`
Owner string `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
AmountPerBoardlot int64 `protobuf:"varint,3,opt,name=amountPerBoardlot,proto3" json:"amountPerBoardlot,omitempty"`
MinBoardlot int64 `protobuf:"varint,4,opt,name=minBoardlot,proto3" json:"minBoardlot,omitempty"`
PricePerBoardlot int64 `protobuf:"varint,5,opt,name=pricePerBoardlot,proto3" json:"pricePerBoardlot,omitempty"`
TotalBoardlot int64 `protobuf:"varint,6,opt,name=totalBoardlot,proto3" json:"totalBoardlot,omitempty"`
TradedBoardlot int64 `protobuf:"varint,7,opt,name=tradedBoardlot,proto3" json:"tradedBoardlot,omitempty"`
BuyID string `protobuf:"bytes,8,opt,name=buyID,proto3" json:"buyID,omitempty"`
Status int32 `protobuf:"varint,9,opt,name=status,proto3" json:"status,omitempty"`
SellID string `protobuf:"bytes,10,opt,name=sellID,proto3" json:"sellID,omitempty"`
TxHash []string `protobuf:"bytes,11,rep,name=txHash,proto3" json:"txHash,omitempty"`
Height int64 `protobuf:"varint,12,opt,name=height,proto3" json:"height,omitempty"`
Key string `protobuf:"bytes,13,opt,name=key,proto3" json:"key,omitempty"`
BlockTime int64 `protobuf:"varint,14,opt,name=blockTime,proto3" json:"blockTime,omitempty"`
IsSellOrder bool `protobuf:"varint,15,opt,name=isSellOrder,proto3" json:"isSellOrder,omitempty"`
AssetExec string `protobuf:"bytes,16,opt,name=assetExec,proto3" json:"assetExec,omitempty"`
TxIndex string `protobuf:"bytes,17,opt,name=txIndex,proto3" json:"txIndex,omitempty"`
IsFinished bool `protobuf:"varint,18,opt,name=isFinished,proto3" json:"isFinished,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *LocalOrder) Reset() { *m = LocalOrder{} }
func (m *LocalOrder) String() string { return proto.CompactTextString(m) }
func (*LocalOrder) ProtoMessage() {}
func (*LocalOrder) Descriptor() ([]byte, []int) {
return fileDescriptor_ee944bd90e8a0312, []int{29}
}
func (m *LocalOrder) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_LocalOrder.Unmarshal(m, b)
}
func (m *LocalOrder) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_LocalOrder.Marshal(b, m, deterministic)
}
func (m *LocalOrder) XXX_Merge(src proto.Message) {
xxx_messageInfo_LocalOrder.Merge(m, src)
}
func (m *LocalOrder) XXX_Size() int {
return xxx_messageInfo_LocalOrder.Size(m)
}
func (m *LocalOrder) XXX_DiscardUnknown() {
xxx_messageInfo_LocalOrder.DiscardUnknown(m)
}
var xxx_messageInfo_LocalOrder proto.InternalMessageInfo
func (m *LocalOrder) GetAssetSymbol() string {
if m != nil {
return m.AssetSymbol
}
return ""
}
func (m *LocalOrder) GetOwner() string {
if m != nil {
return m.Owner
}
return ""
}
func (m *LocalOrder) GetAmountPerBoardlot() int64 {
if m != nil {
return m.AmountPerBoardlot
}
return 0
}
func (m *LocalOrder) GetMinBoardlot() int64 {
if m != nil {
return m.MinBoardlot
}
return 0
}
func (m *LocalOrder) GetPricePerBoardlot() int64 {
if m != nil {
return m.PricePerBoardlot
}
return 0
}
func (m *LocalOrder) GetTotalBoardlot() int64 {
if m != nil {
return m.TotalBoardlot
}
return 0
}
func (m *LocalOrder) GetTradedBoardlot() int64 {
if m != nil {
return m.TradedBoardlot
}
return 0
}
func (m *LocalOrder) GetBuyID() string {
if m != nil {
return m.BuyID
}
return ""
}
func (m *LocalOrder) GetStatus() int32 {
if m != nil {
return m.Status
}
return 0
}
func (m *LocalOrder) GetSellID() string {
if m != nil {
return m.SellID
}
return ""
}
func (m *LocalOrder) GetTxHash() []string {
if m != nil {
return m.TxHash
}
return nil
}
func (m *LocalOrder) GetHeight() int64 {
if m != nil {
return m.Height
}
return 0
}
func (m *LocalOrder) GetKey() string {
if m != nil {
return m.Key
}
return ""
}
func (m *LocalOrder) GetBlockTime() int64 {
if m != nil {
return m.BlockTime
}
return 0
}
func (m *LocalOrder) GetIsSellOrder() bool {
if m != nil {
return m.IsSellOrder
}
return false
}
func (m *LocalOrder) GetAssetExec() string {
if m != nil {
return m.AssetExec
}
return ""
}
func (m *LocalOrder) GetTxIndex() string {
if m != nil {
return m.TxIndex
}
return ""
}
func (m *LocalOrder) GetIsFinished() bool {
if m != nil {
return m.IsFinished
}
return false
}
func init() { func init() {
proto.RegisterType((*Trade)(nil), "types.Trade") proto.RegisterType((*Trade)(nil), "types.Trade")
proto.RegisterType((*TradeForSell)(nil), "types.TradeForSell") proto.RegisterType((*TradeForSell)(nil), "types.TradeForSell")
...@@ -2440,90 +2615,95 @@ func init() { ...@@ -2440,90 +2615,95 @@ func init() {
proto.RegisterType((*ReqSellToken)(nil), "types.ReqSellToken") proto.RegisterType((*ReqSellToken)(nil), "types.ReqSellToken")
proto.RegisterType((*ReqRevokeSell)(nil), "types.ReqRevokeSell") proto.RegisterType((*ReqRevokeSell)(nil), "types.ReqRevokeSell")
proto.RegisterType((*ReqBuyToken)(nil), "types.ReqBuyToken") proto.RegisterType((*ReqBuyToken)(nil), "types.ReqBuyToken")
proto.RegisterType((*LocalOrder)(nil), "types.LocalOrder")
} }
func init() { proto.RegisterFile("trade.proto", fileDescriptor_ee944bd90e8a0312) } func init() { proto.RegisterFile("trade.proto", fileDescriptor_ee944bd90e8a0312) }
var fileDescriptor_ee944bd90e8a0312 = []byte{ var fileDescriptor_ee944bd90e8a0312 = []byte{
// 1246 bytes of a gzipped FileDescriptorProto // 1306 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x58, 0x41, 0x6f, 0xdb, 0x36, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x59, 0x41, 0x6f, 0xe3, 0x44,
0x14, 0x8e, 0x2d, 0xcb, 0xb1, 0x9e, 0x63, 0xc7, 0x61, 0xdd, 0x4c, 0x0d, 0x76, 0x30, 0x84, 0x62, 0x14, 0x6e, 0xe2, 0x38, 0x8d, 0x5f, 0x9a, 0x34, 0x9d, 0xcd, 0x16, 0x6f, 0x85, 0x50, 0x64, 0xad,
0x4b, 0x8b, 0x22, 0xc0, 0x52, 0x14, 0x18, 0xb0, 0x61, 0x43, 0x9c, 0xac, 0x73, 0xb7, 0x16, 0x1b, 0xa0, 0xbb, 0x5a, 0x55, 0xa2, 0xab, 0x95, 0x90, 0x40, 0xa0, 0xa6, 0xa5, 0xa4, 0xd0, 0x15, 0x68,
0x18, 0x0f, 0xd8, 0x55, 0xb6, 0xd8, 0x44, 0x88, 0x6c, 0x39, 0x22, 0xd5, 0x58, 0x7f, 0x65, 0x3b, 0x1a, 0x24, 0xae, 0x4e, 0x3c, 0xdb, 0x5a, 0x75, 0xec, 0xd4, 0x1e, 0x6f, 0xe3, 0xbf, 0x02, 0x07,
0xec, 0xb6, 0xcb, 0xae, 0x03, 0xf6, 0x17, 0x06, 0xec, 0xef, 0xf4, 0x52, 0xec, 0x52, 0x90, 0xa2, 0x6e, 0x5c, 0x90, 0x38, 0x21, 0xf1, 0x17, 0x90, 0xf8, 0x3b, 0x7b, 0x59, 0x71, 0x41, 0x33, 0x9e,
0x25, 0x4a, 0x96, 0x52, 0x1b, 0xe8, 0x21, 0x4d, 0x6f, 0xe6, 0xe3, 0xe3, 0xc7, 0xa7, 0xef, 0x7b, 0xd8, 0x63, 0xc7, 0xee, 0x26, 0xd2, 0x1e, 0xba, 0x5d, 0x6e, 0x99, 0x37, 0x6f, 0xbe, 0x79, 0xfe,
0x8f, 0x7c, 0x26, 0x34, 0x59, 0x60, 0x3b, 0xe4, 0x60, 0x16, 0xf8, 0xcc, 0x47, 0x3a, 0x8b, 0x66, 0xbe, 0xf7, 0x66, 0x9e, 0x1d, 0x68, 0x52, 0xdf, 0xb4, 0xc8, 0xde, 0xd4, 0xf7, 0xa8, 0x87, 0x54,
0x84, 0xee, 0xed, 0xb0, 0xc0, 0x9e, 0x52, 0x7b, 0xcc, 0x5c, 0x7f, 0x1a, 0xcf, 0x58, 0xaf, 0xab, 0x1a, 0x4d, 0x49, 0xb0, 0xb3, 0x45, 0x7d, 0xd3, 0x0d, 0xcc, 0x31, 0xb5, 0x3d, 0x37, 0x9e, 0x31,
0xa0, 0x0f, 0xb9, 0x27, 0x7a, 0x0c, 0x06, 0x25, 0x9e, 0xf7, 0xdc, 0x9d, 0xb8, 0xcc, 0xac, 0xf4, 0x5e, 0x55, 0x41, 0x1d, 0x32, 0x4f, 0xf4, 0x14, 0xb4, 0x80, 0x38, 0xce, 0xa9, 0x3d, 0xb1, 0xa9,
0x2a, 0xfb, 0xcd, 0xc3, 0x3b, 0x07, 0x62, 0xdd, 0x81, 0x70, 0x78, 0xea, 0x07, 0xa7, 0xc4, 0xf3, 0x5e, 0xe9, 0x55, 0x76, 0x9b, 0xfb, 0xf7, 0xf6, 0xf8, 0xba, 0x3d, 0xee, 0x70, 0xec, 0xf9, 0x67,
0x06, 0x1b, 0x38, 0xf5, 0x43, 0x87, 0x60, 0x8c, 0xc2, 0xe8, 0x85, 0x1d, 0x5c, 0x10, 0x66, 0x56, 0xc4, 0x71, 0x06, 0x6b, 0x38, 0xf5, 0x43, 0xfb, 0xa0, 0x8d, 0xc2, 0xe8, 0xb9, 0xe9, 0x5f, 0x12,
0xc5, 0x22, 0x94, 0x5b, 0xd4, 0x0f, 0x23, 0xbe, 0x26, 0x71, 0x43, 0x5f, 0x01, 0x04, 0xe4, 0x95, 0xaa, 0x57, 0xf9, 0x22, 0x94, 0x5b, 0xd4, 0x0f, 0x23, 0xb6, 0x26, 0x71, 0x43, 0x9f, 0x03, 0xf8,
0x7f, 0x41, 0x38, 0x9c, 0xa9, 0x89, 0x45, 0xf7, 0x72, 0x8b, 0x70, 0xe2, 0x30, 0xd8, 0xc0, 0x8a, 0xe4, 0xa5, 0x77, 0x49, 0x18, 0x9c, 0xae, 0xf0, 0x45, 0x0f, 0x72, 0x8b, 0x70, 0xe2, 0x30, 0x58,
0x3b, 0x7a, 0x02, 0x8d, 0x51, 0x18, 0xc5, 0x41, 0xea, 0x62, 0xe9, 0x27, 0xcb, 0xfb, 0x89, 0xe9, 0xc3, 0x92, 0x3b, 0x7a, 0x06, 0x8d, 0x51, 0x18, 0xc5, 0x41, 0xaa, 0x7c, 0xe9, 0x07, 0x8b, 0xfb,
0xc1, 0x06, 0x4e, 0x5c, 0xf9, 0x9e, 0x3c, 0x68, 0x19, 0x68, 0xbd, 0x70, 0xcf, 0xd3, 0xc4, 0x81, 0xf1, 0xe9, 0xc1, 0x1a, 0x4e, 0x5c, 0xd9, 0x9e, 0x2c, 0x68, 0x11, 0x68, 0xbd, 0x70, 0xcf, 0xb3,
0xef, 0x99, 0xba, 0xa3, 0x2f, 0xc1, 0x88, 0x23, 0xe8, 0x87, 0x91, 0xb9, 0x29, 0xd6, 0x9a, 0x85, 0xc4, 0x81, 0xed, 0x99, 0xba, 0xa3, 0xcf, 0x40, 0x8b, 0x23, 0xe8, 0x87, 0x91, 0xbe, 0xce, 0xd7,
0xf1, 0xca, 0x4f, 0x4d, 0x9c, 0x51, 0x1b, 0xaa, 0x2c, 0x32, 0x6b, 0xbd, 0xca, 0xbe, 0x8e, 0xab, 0xea, 0x85, 0xf1, 0x8a, 0x47, 0x4d, 0x9c, 0x51, 0x1b, 0xaa, 0x34, 0xd2, 0x6b, 0xbd, 0xca, 0xae,
0x2c, 0xea, 0x6f, 0x82, 0xfe, 0xca, 0xf6, 0x42, 0x62, 0xfd, 0x5b, 0x85, 0x2d, 0x75, 0x5f, 0xd4, 0x8a, 0xab, 0x34, 0xea, 0xaf, 0x83, 0xfa, 0xd2, 0x74, 0x42, 0x62, 0xfc, 0x5d, 0x85, 0x0d, 0x79,
0x83, 0x26, 0xf3, 0x2f, 0xc8, 0xf4, 0x34, 0x9a, 0x8c, 0x7c, 0x4f, 0xf0, 0x6f, 0x60, 0xd5, 0x84, 0x5f, 0xd4, 0x83, 0x26, 0xf5, 0x2e, 0x89, 0x7b, 0x16, 0x4d, 0x46, 0x9e, 0xc3, 0xf9, 0xd7, 0xb0,
0x1e, 0xc1, 0x8e, 0x3d, 0xf1, 0xc3, 0x29, 0xfb, 0x99, 0x04, 0x7d, 0xdf, 0x0e, 0x1c, 0xcf, 0x8f, 0x6c, 0x42, 0x4f, 0x60, 0xcb, 0x9c, 0x78, 0xa1, 0x4b, 0x7f, 0x20, 0x7e, 0xdf, 0x33, 0x7d, 0xcb,
0x29, 0xd7, 0xf0, 0xf2, 0x04, 0xc7, 0x9b, 0xb8, 0xd3, 0xc4, 0x4f, 0x13, 0x7e, 0xaa, 0x09, 0x3d, 0xf1, 0x62, 0xca, 0x15, 0xbc, 0x38, 0xc1, 0xf0, 0x26, 0xb6, 0x9b, 0xf8, 0x29, 0xdc, 0x4f, 0x36,
0x84, 0xce, 0x2c, 0x70, 0xc7, 0x44, 0x85, 0xab, 0x09, 0xb7, 0x25, 0x3b, 0xba, 0x0f, 0x2d, 0xe6, 0xa1, 0xc7, 0xd0, 0x99, 0xfa, 0xf6, 0x98, 0xc8, 0x70, 0x35, 0xee, 0xb6, 0x60, 0x47, 0x0f, 0xa1,
0x33, 0xdb, 0x4b, 0x1c, 0x75, 0xe1, 0x98, 0x35, 0xa2, 0x4f, 0xc1, 0xa0, 0xcc, 0x0e, 0x18, 0x73, 0x45, 0x3d, 0x6a, 0x3a, 0x89, 0xa3, 0xca, 0x1d, 0xb3, 0x46, 0xf4, 0x21, 0x68, 0x01, 0x35, 0x7d,
0x27, 0x44, 0x70, 0xac, 0xe1, 0xd4, 0x80, 0xf6, 0xa0, 0x41, 0x99, 0x3f, 0x13, 0x93, 0x9b, 0x62, 0x4a, 0xed, 0x09, 0xe1, 0x1c, 0x2b, 0x38, 0x35, 0xa0, 0x1d, 0x68, 0x04, 0xd4, 0x9b, 0xf2, 0xc9,
0x32, 0x19, 0xf3, 0x95, 0xe3, 0xc0, 0xbf, 0x72, 0x5e, 0x86, 0x53, 0xc7, 0x6c, 0xf4, 0x2a, 0xfb, 0x75, 0x3e, 0x99, 0x8c, 0xd9, 0xca, 0xb1, 0xef, 0x5d, 0x5b, 0x2f, 0x42, 0xd7, 0xd2, 0x1b, 0xbd,
0x0d, 0x9c, 0x1a, 0xf8, 0xac, 0x4d, 0x29, 0x61, 0xdf, 0xcd, 0xc9, 0xd8, 0x34, 0x04, 0x33, 0xa9, 0xca, 0x6e, 0x03, 0xa7, 0x06, 0x36, 0x6b, 0x06, 0x01, 0xa1, 0x5f, 0xcf, 0xc8, 0x58, 0xd7, 0x38,
0xc1, 0xfa, 0x1e, 0x9a, 0x8a, 0xf4, 0x68, 0x17, 0xea, 0x5c, 0xba, 0x67, 0x27, 0x92, 0x43, 0x39, 0x33, 0xa9, 0xc1, 0xf8, 0x06, 0x9a, 0x92, 0xf4, 0x68, 0x1b, 0xea, 0x4c, 0xba, 0x93, 0x23, 0xc1,
0xe2, 0x84, 0x8c, 0x64, 0xa0, 0xc7, 0xd3, 0x05, 0x71, 0xaa, 0xc9, 0x7a, 0x04, 0x68, 0x39, 0xfd, 0xa1, 0x18, 0x31, 0x42, 0x46, 0x22, 0xd0, 0x43, 0x77, 0x4e, 0x9c, 0x6c, 0x32, 0x9e, 0x00, 0x5a,
0xca, 0xf0, 0xac, 0xd7, 0x15, 0xe8, 0xe4, 0x53, 0xee, 0xb6, 0xa8, 0x98, 0xb2, 0x5d, 0xcf, 0xb3, 0x4c, 0xbf, 0x32, 0x3c, 0xe3, 0x55, 0x05, 0x3a, 0xf9, 0x94, 0xbb, 0x2b, 0x2a, 0xa6, 0x6c, 0xd7,
0xfd, 0x3c, 0x25, 0x29, 0xad, 0x17, 0xd4, 0x05, 0x7d, 0x14, 0x46, 0x09, 0x47, 0xf1, 0x60, 0x05, 0xf3, 0x6c, 0x9f, 0xa6, 0x24, 0xa5, 0xf5, 0x82, 0xba, 0xa0, 0x8e, 0xc2, 0x28, 0xe1, 0x28, 0x1e,
0xca, 0x1f, 0xc0, 0xce, 0x52, 0x05, 0x15, 0x83, 0x59, 0xff, 0x69, 0x60, 0xf0, 0x1d, 0x7f, 0x0a, 0x2c, 0x41, 0xf9, 0x23, 0xd8, 0x5a, 0xa8, 0xa0, 0x62, 0x30, 0xe3, 0x1f, 0x05, 0x34, 0xb6, 0xe3,
0x1c, 0x12, 0xac, 0x40, 0xb4, 0x09, 0x9b, 0xb6, 0xe3, 0x04, 0x84, 0x52, 0xb1, 0xb1, 0x81, 0x17, 0xf7, 0xbe, 0x45, 0xfc, 0x25, 0x88, 0xd6, 0x61, 0xdd, 0xb4, 0x2c, 0x9f, 0x04, 0x01, 0xdf, 0x58,
0xc3, 0x62, 0x09, 0xb4, 0x15, 0x25, 0xa8, 0xad, 0x26, 0x81, 0xbe, 0xaa, 0x04, 0xf5, 0x22, 0x09, 0xc3, 0xf3, 0x61, 0xb1, 0x04, 0xca, 0x92, 0x12, 0xd4, 0x96, 0x93, 0x40, 0x5d, 0x56, 0x82, 0x7a,
0x2c, 0xd8, 0xa2, 0xbe, 0xe7, 0x24, 0x4e, 0x71, 0xb9, 0x64, 0x6c, 0xd9, 0x62, 0x6b, 0x5c, 0x57, 0x91, 0x04, 0x06, 0x6c, 0x04, 0x9e, 0x63, 0x25, 0x4e, 0x71, 0xb9, 0x64, 0x6c, 0xd9, 0x62, 0x6b,
0x6c, 0xc6, 0x75, 0xc5, 0x06, 0xf9, 0x62, 0x4b, 0xf3, 0xbd, 0x99, 0xa9, 0x1f, 0x6e, 0x67, 0x36, 0xdc, 0x54, 0x6c, 0xda, 0x4d, 0xc5, 0x06, 0xf9, 0x62, 0x4b, 0xf3, 0xbd, 0x99, 0xa9, 0x1f, 0x66,
0x0b, 0xa9, 0xb9, 0x25, 0x8e, 0x33, 0x39, 0xe2, 0xf6, 0x73, 0xe2, 0x9e, 0x9d, 0x33, 0xb3, 0x25, 0xa7, 0x26, 0x0d, 0x03, 0x7d, 0x83, 0x1f, 0x67, 0x62, 0xc4, 0xec, 0x17, 0xc4, 0x3e, 0xbf, 0xa0,
0xf6, 0x91, 0xa3, 0x6c, 0x1a, 0xb5, 0xf3, 0x69, 0xf4, 0xa6, 0x0a, 0xad, 0x45, 0xd5, 0x7c, 0x0c, 0x7a, 0x8b, 0xef, 0x23, 0x46, 0xd9, 0x34, 0x6a, 0xe7, 0xd3, 0xe8, 0x75, 0x15, 0x5a, 0xf3, 0xaa,
0x8a, 0x7e, 0x06, 0xed, 0x91, 0x1f, 0x9e, 0x9d, 0xb3, 0x9c, 0xa6, 0x39, 0x6b, 0x9a, 0xfb, 0x0d, 0x79, 0x1f, 0x14, 0xfd, 0x18, 0xda, 0x23, 0x2f, 0x3c, 0xbf, 0xa0, 0x39, 0x4d, 0x73, 0xd6, 0x34,
0xb5, 0x90, 0x52, 0xee, 0x8d, 0x12, 0xee, 0xa1, 0x9c, 0xfb, 0x66, 0x9e, 0xfb, 0x3f, 0x35, 0x68, 0xf7, 0x1b, 0x72, 0x21, 0xa5, 0xdc, 0x6b, 0x25, 0xdc, 0x43, 0x39, 0xf7, 0xcd, 0x3c, 0xf7, 0xbf,
0x63, 0x32, 0x26, 0xee, 0x8c, 0xf5, 0xc3, 0xa8, 0x6f, 0x53, 0xb2, 0x02, 0xf9, 0x5d, 0xd0, 0xfd, 0x29, 0xd0, 0xc6, 0x64, 0x4c, 0xec, 0x29, 0xed, 0x87, 0x51, 0xdf, 0x0c, 0xc8, 0x12, 0xe4, 0x77,
0xab, 0x29, 0x09, 0x24, 0xf5, 0xf1, 0xa0, 0x9c, 0x78, 0xe3, 0xfd, 0x12, 0x6f, 0xdc, 0x08, 0xe2, 0x41, 0xf5, 0xae, 0x5d, 0xe2, 0x0b, 0xea, 0xe3, 0x41, 0x39, 0xf1, 0xda, 0xdb, 0x25, 0x5e, 0xbb,
0x0d, 0x95, 0x78, 0x59, 0x24, 0x90, 0x2f, 0x12, 0x36, 0x1f, 0xd8, 0xf4, 0x7c, 0x51, 0x3c, 0xf1, 0x15, 0xc4, 0x6b, 0x32, 0xf1, 0xa2, 0x48, 0x20, 0x5f, 0x24, 0x74, 0x36, 0x30, 0x83, 0x8b, 0x79,
0x48, 0x11, 0x6a, 0xab, 0x5c, 0xa8, 0x56, 0x5e, 0xa8, 0xff, 0x35, 0xd8, 0x96, 0x42, 0xf1, 0x93, 0xf1, 0xc4, 0x23, 0x49, 0xa8, 0x8d, 0x72, 0xa1, 0x5a, 0x79, 0xa1, 0xfe, 0x55, 0x60, 0x53, 0x08,
0xef, 0x96, 0x2b, 0x75, 0xf3, 0x0f, 0xbd, 0x54, 0xff, 0x24, 0x5b, 0x5a, 0xb9, 0x6c, 0x91, 0xea, 0xc5, 0x4e, 0xbe, 0x3b, 0xae, 0xd4, 0xed, 0x3f, 0xf4, 0x52, 0xfd, 0x93, 0x6c, 0x69, 0xe5, 0xb2,
0xb7, 0x4b, 0xd4, 0xdf, 0x2e, 0x57, 0xbf, 0x93, 0x57, 0xbf, 0x0f, 0x77, 0xa5, 0xf8, 0xe2, 0x8a, 0x45, 0xa8, 0xdf, 0x2e, 0x51, 0x7f, 0xb3, 0x5c, 0xfd, 0x4e, 0x5e, 0xfd, 0x3e, 0xdc, 0x17, 0xe2,
0xec, 0x27, 0xfd, 0xf3, 0x03, 0xa8, 0x8d, 0x6c, 0x4a, 0x64, 0x8f, 0x7e, 0x57, 0x76, 0xa2, 0xd9, 0xf3, 0x2b, 0xb2, 0x9f, 0xf4, 0xcf, 0x8f, 0xa0, 0x36, 0x32, 0x03, 0x22, 0x7a, 0xf4, 0xfb, 0xa2,
0x8a, 0xc6, 0xc2, 0xc5, 0x3a, 0x82, 0x6e, 0x0e, 0x23, 0xee, 0x53, 0xd6, 0x80, 0x58, 0x0e, 0x23, 0x13, 0xcd, 0x56, 0x34, 0xe6, 0x2e, 0xc6, 0x01, 0x74, 0x73, 0x18, 0x71, 0x9f, 0xb2, 0x02, 0xc4,
0xbe, 0xa9, 0xd7, 0xc1, 0x38, 0xce, 0x62, 0x9c, 0x26, 0x7f, 0x1f, 0x1e, 0x66, 0x30, 0x76, 0xb3, 0x62, 0x18, 0xf1, 0x4d, 0xbd, 0x0a, 0xc6, 0x61, 0x16, 0xe3, 0x2c, 0x79, 0x7d, 0x78, 0x9c, 0xc1,
0x18, 0x8b, 0x9c, 0x97, 0x20, 0xdf, 0xc2, 0x8e, 0x32, 0x21, 0xb9, 0x58, 0x07, 0xe0, 0x04, 0x76, 0xd8, 0xce, 0x62, 0xcc, 0x73, 0x5e, 0x80, 0x7c, 0x05, 0x5b, 0xd2, 0x84, 0xe0, 0x62, 0x15, 0x80,
0xf3, 0x51, 0xc8, 0x4f, 0x59, 0x07, 0xe5, 0x8f, 0x0a, 0xb4, 0x30, 0xb9, 0x3c, 0x72, 0x9c, 0xe0, 0x23, 0xd8, 0xce, 0x47, 0x21, 0x1e, 0x65, 0x15, 0x94, 0x5f, 0x2b, 0xd0, 0xc2, 0xe4, 0xea, 0xc0,
0x88, 0x6b, 0x45, 0x11, 0x82, 0x1a, 0xbf, 0x88, 0x64, 0x2d, 0x8a, 0xdf, 0x4a, 0xe2, 0x54, 0x33, 0xb2, 0xfc, 0x03, 0xa6, 0x55, 0x80, 0x10, 0xd4, 0xd8, 0x45, 0x24, 0x6a, 0x91, 0xff, 0x96, 0x12,
0x27, 0x76, 0x17, 0x74, 0x51, 0xab, 0xa6, 0xd6, 0xd3, 0x78, 0xe2, 0x88, 0x01, 0x4f, 0x04, 0xc7, 0xa7, 0x9a, 0x39, 0xb1, 0xbb, 0xa0, 0xf2, 0x5a, 0xd5, 0x95, 0x9e, 0xc2, 0x12, 0x87, 0x0f, 0x58,
0x0d, 0x88, 0xf8, 0x5f, 0x26, 0xff, 0x2d, 0xa4, 0x06, 0xbe, 0x66, 0xcc, 0x0b, 0x54, 0xd4, 0x97, 0x22, 0x58, 0xb6, 0x4f, 0xf8, 0x7b, 0x99, 0x78, 0x5b, 0x48, 0x0d, 0x6c, 0xcd, 0x98, 0x15, 0x28,
0x8e, 0xe3, 0x01, 0xbf, 0x0d, 0x5f, 0x06, 0xfe, 0xe4, 0x47, 0x12, 0xc9, 0x26, 0x6d, 0x31, 0xb4, 0xaf, 0x2f, 0x15, 0xc7, 0x03, 0x76, 0x1b, 0xbe, 0xf0, 0xbd, 0xc9, 0x77, 0x24, 0x12, 0x4d, 0xda,
0x7e, 0xaf, 0x70, 0xa6, 0x2e, 0x87, 0xe2, 0x4c, 0x58, 0xaf, 0x63, 0x5a, 0x20, 0x56, 0x33, 0x88, 0x7c, 0x68, 0xfc, 0x52, 0x61, 0x4c, 0x5d, 0x0d, 0xf9, 0x99, 0xb0, 0x5a, 0xc7, 0x34, 0x47, 0xac,
0x69, 0x04, 0x9a, 0x1a, 0xc1, 0xf5, 0x51, 0xa7, 0x0c, 0xe8, 0x2a, 0x03, 0xd6, 0x6f, 0x15, 0xe8, 0x66, 0x10, 0xd3, 0x08, 0x14, 0x39, 0x82, 0x9b, 0xa3, 0x4e, 0x19, 0x50, 0x65, 0x06, 0x8c, 0x9f,
0x2c, 0xa2, 0xeb, 0x87, 0xd1, 0xcd, 0x0a, 0xee, 0x1f, 0x8d, 0x8b, 0x3b, 0xf3, 0xa2, 0x35, 0x22, 0x2b, 0xd0, 0x99, 0x47, 0xd7, 0x0f, 0xa3, 0xdb, 0x15, 0xdc, 0x5f, 0x0a, 0x13, 0x77, 0xea, 0x44,
0x5b, 0xf3, 0xbc, 0xbd, 0xfd, 0x2d, 0xc9, 0x7b, 0xb9, 0x19, 0x3b, 0xa0, 0x5d, 0x90, 0x48, 0x9e, 0x2b, 0x44, 0xb6, 0xe2, 0x79, 0x7b, 0xf7, 0x5b, 0x92, 0xb7, 0x72, 0x33, 0x76, 0x40, 0xb9, 0x24,
0xaf, 0xfc, 0xe7, 0x3b, 0x1a, 0xca, 0xbf, 0x45, 0x53, 0x33, 0xf3, 0xa2, 0x75, 0x32, 0xfe, 0x43, 0x91, 0x38, 0x5f, 0xd9, 0xcf, 0x37, 0x34, 0x94, 0x7f, 0xf2, 0xa6, 0x66, 0xea, 0x44, 0xab, 0x64,
0x95, 0x6e, 0x95, 0xab, 0xf2, 0xc3, 0x90, 0x6d, 0xc0, 0x3b, 0x1c, 0x55, 0x35, 0x8a, 0x9e, 0xc4, 0xfc, 0xbb, 0x2a, 0xdd, 0x32, 0x57, 0xe5, 0xbb, 0x21, 0xdb, 0x80, 0x75, 0x38, 0xb2, 0x6a, 0x01,
0x4f, 0x35, 0xf1, 0xc8, 0xac, 0xf4, 0xb4, 0xcc, 0xed, 0xa2, 0xfa, 0x62, 0xc5, 0xd1, 0x3a, 0x91, 0x7a, 0x16, 0x7f, 0xaa, 0x89, 0x47, 0x7a, 0xa5, 0xa7, 0x64, 0x6e, 0x17, 0xd9, 0x17, 0x4b, 0x8e,
0xfa, 0x2f, 0x2a, 0x97, 0xca, 0xb7, 0xa9, 0x0c, 0x4e, 0x57, 0xc5, 0x59, 0x78, 0xe2, 0xd4, 0xcd, 0xc6, 0x91, 0xd0, 0x7f, 0x5e, 0xb9, 0x81, 0xf8, 0x36, 0x95, 0xc1, 0xe9, 0xca, 0x38, 0x73, 0x4f,
0x7a, 0xa3, 0xc9, 0x80, 0xc4, 0x15, 0xf1, 0x11, 0x1c, 0x01, 0xe2, 0x95, 0x30, 0x9f, 0x49, 0x39, 0x9c, 0xba, 0x19, 0xaf, 0x15, 0x11, 0x10, 0xbf, 0x22, 0xde, 0x83, 0x23, 0x80, 0x7f, 0x25, 0xcc,
0xeb, 0x4d, 0xca, 0xa5, 0x91, 0xe7, 0x8f, 0x2f, 0x86, 0xbc, 0xc3, 0x6b, 0xc7, 0xed, 0x5f, 0x62, 0x67, 0x52, 0xce, 0x7a, 0x9b, 0x72, 0x69, 0xe4, 0x78, 0xe3, 0xcb, 0x21, 0xeb, 0xf0, 0xda, 0x71,
0xe0, 0x0c, 0xba, 0x34, 0x49, 0x0e, 0xd1, 0x6b, 0x35, 0xb0, 0x6a, 0x7a, 0x67, 0xc3, 0xd5, 0xc9, 0xfb, 0x97, 0x18, 0x18, 0x83, 0x76, 0x90, 0x24, 0x07, 0xef, 0xb5, 0x1a, 0x58, 0x36, 0xbd, 0xb1,
0x49, 0x4f, 0xd1, 0x01, 0xd4, 0x7d, 0x35, 0x81, 0x76, 0xd5, 0x04, 0x4a, 0x1d, 0xb1, 0xf4, 0xb2, 0xe1, 0xea, 0xe4, 0xa4, 0x0f, 0xd0, 0x1e, 0xd4, 0x3d, 0x39, 0x81, 0xb6, 0xe5, 0x04, 0x4a, 0x1d,
0x5e, 0xc0, 0x16, 0x26, 0x97, 0x7c, 0x47, 0x71, 0xc1, 0xa1, 0xcf, 0xa1, 0xc6, 0xbf, 0xfe, 0x9a, 0xb1, 0xf0, 0x32, 0x9e, 0xc3, 0x06, 0x26, 0x57, 0x6c, 0x47, 0x7e, 0xc1, 0xa1, 0x4f, 0xa0, 0xc6,
0xf7, 0x54, 0x2c, 0x1c, 0x8a, 0x53, 0xc8, 0xfa, 0x55, 0xf4, 0x1a, 0xca, 0x6b, 0xd4, 0x17, 0x50, 0x9e, 0xfe, 0x86, 0xef, 0xa9, 0x98, 0x3b, 0x14, 0xa7, 0x90, 0xf1, 0x13, 0xef, 0x35, 0xa4, 0xaf,
0x8f, 0x5f, 0x17, 0x25, 0x62, 0xf9, 0xbb, 0x29, 0x96, 0x8e, 0x25, 0xc8, 0xcf, 0xa0, 0x89, 0xc9, 0x51, 0x9f, 0x42, 0x3d, 0xfe, 0xba, 0x28, 0x10, 0xcb, 0xbf, 0x9b, 0x62, 0xe1, 0x58, 0x82, 0x7c,
0x65, 0x3f, 0x8c, 0xe2, 0x38, 0xef, 0x83, 0x36, 0x0a, 0x23, 0x09, 0x5a, 0xf0, 0x82, 0x8b, 0xf9, 0x02, 0x4d, 0x4c, 0xae, 0xfa, 0x61, 0x14, 0xc7, 0xf9, 0x10, 0x94, 0x51, 0x18, 0x09, 0xd0, 0x82,
0xb4, 0xcc, 0x83, 0x14, 0x4a, 0x0c, 0x0e, 0xff, 0xd2, 0x40, 0x17, 0x09, 0x83, 0xbe, 0x81, 0xee, 0x2f, 0xb8, 0x98, 0x4d, 0x8b, 0x3c, 0x48, 0xa1, 0xf8, 0xc0, 0xf8, 0xa3, 0x06, 0x70, 0xea, 0x8d,
0x71, 0x40, 0x6c, 0x46, 0xb0, 0x7d, 0x95, 0xf4, 0x58, 0xc3, 0x39, 0x2a, 0xfa, 0xee, 0xbd, 0x6d, 0xcd, 0xf4, 0xd8, 0xe5, 0x9c, 0x66, 0xcb, 0x45, 0x32, 0xfd, 0x5f, 0x2e, 0x2b, 0x97, 0x8b, 0x72,
0x69, 0xfc, 0x65, 0x4a, 0xdd, 0xb3, 0xe9, 0x70, 0x6e, 0x6d, 0xa0, 0xaf, 0xe1, 0x4e, 0x76, 0x3d, 0xfb, 0xca, 0x85, 0x75, 0x64, 0x74, 0x76, 0xe2, 0x5a, 0x64, 0xa6, 0x6f, 0xc5, 0x1d, 0x99, 0x18,
0x8f, 0x6f, 0x8e, 0x0a, 0xe2, 0x29, 0x5a, 0xfd, 0x14, 0x76, 0xb3, 0xab, 0x63, 0x32, 0x86, 0x73, 0xa2, 0x8f, 0x00, 0xec, 0xe0, 0xd8, 0x76, 0xed, 0xe0, 0x82, 0x58, 0x3a, 0xe2, 0xc0, 0x92, 0x65,
0x54, 0xce, 0x52, 0x31, 0x8e, 0xb9, 0x14, 0x85, 0x68, 0x57, 0x87, 0x73, 0x54, 0xf6, 0xd8, 0x5c, 0xff, 0x77, 0x05, 0x54, 0x4e, 0x19, 0xfa, 0x12, 0xba, 0x87, 0x3e, 0x31, 0x29, 0xc1, 0xe6, 0x75,
0x84, 0xf3, 0x03, 0xec, 0x2d, 0xb3, 0x11, 0xf7, 0xad, 0x05, 0x31, 0xa5, 0x93, 0x45, 0x58, 0x03, 0xd2, 0x94, 0x0f, 0x67, 0xa8, 0xa8, 0x50, 0x76, 0x36, 0x85, 0xf1, 0x47, 0x37, 0xb0, 0xcf, 0xdd,
0xb8, 0x57, 0xf4, 0x6d, 0x31, 0x3f, 0xa5, 0x8f, 0xd1, 0x05, 0x48, 0xa3, 0xba, 0x78, 0xf7, 0x7f, 0xe1, 0xcc, 0x58, 0x43, 0x5f, 0xc0, 0xbd, 0xec, 0x7a, 0x96, 0xd0, 0x33, 0x54, 0x90, 0xc0, 0x45,
0xfc, 0x36, 0x00, 0x00, 0xff, 0xff, 0x84, 0x9a, 0xb6, 0x92, 0x20, 0x18, 0x00, 0x00, 0xab, 0x8f, 0x61, 0x3b, 0xbb, 0x3a, 0xae, 0x9e, 0xe1, 0x0c, 0x95, 0x97, 0x55, 0x31, 0x8e, 0xbe,
0x10, 0x05, 0x7f, 0xbf, 0x19, 0xce, 0x50, 0xd9, 0xbf, 0x13, 0x45, 0x38, 0xdf, 0xc2, 0xce, 0x22,
0x1b, 0xf1, 0x8b, 0x4e, 0x41, 0x4c, 0xe9, 0x64, 0x11, 0xd6, 0x00, 0x1e, 0x14, 0x3d, 0x5b, 0xcc,
0x4f, 0xe9, 0xbf, 0x17, 0x05, 0x48, 0xa3, 0x3a, 0xff, 0xa3, 0xe8, 0xe9, 0x7f, 0x01, 0x00, 0x00,
0xff, 0xff, 0xe7, 0xe7, 0x49, 0x74, 0x51, 0x1a, 0x00, 0x00,
} }
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment