Commit 3ffe72e7 authored by harrylee's avatar harrylee Committed by vipwzw

ajust code for exchange

parent f28a7e39
......@@ -28,3 +28,23 @@ QueryOrderList|根据用户地址和订单状态(ordered,completed,revoked),
4|价格相同按先进先出的原则进行撮合
5|出于系统安全考虑,最大撮合深度为100单,单笔挂单最小为1e8,就是一个bty
**表结构说明**
表名|主键|索引|用途|说明
---|---|---|---|---
depth|price|nil|动态记录市场深度|主键price是复合主键由{leftAsset}:{rightAsset}:{op}:{price}构成
order|orderID|market_order,addr_status|实时动态维护更新市场上的挂单|market_order是复合索引由{leftAsset}:{rightAsset}:{op}:{price}:{orderID},addr_status是复合索引由{addr}:{status},当订单成交或者撤回时,该条订单记录和索引会从order表中自动删除
history|index|name,addr_status|实时记录某资产交易对下面最新完成的订单信息(revoked状态的交易也会记录)|name是复合索引由{leftAsset}:{rightAsset}构成, addr_status是复合索引由{addr}:{status}
**表中相关参数说明**
参数名|说明
----|----
leftAsset|交易对左边资产名称
rightAsset|交易对右边资产名称
op|买卖操作 1为买,2为卖
status|挂单状态,0 ordered, 1 completed,2 revoked
price|挂单价格,占位16 %016d,为了兼容不同架构的系统,这里设计为整型,由原有浮点型乘以1e8。 比如某交易对在中心化交易所上面是0.25,这里就变成25000000,price取值范围为1<=price<=1e16的整数
orderID|单号,由系统自动生成,整型,占位22 %022d
index|系统自动生成的index,占位22 %022d
......@@ -60,7 +60,7 @@ func (e *exchange) CheckTx(tx *types.Transaction, index int) error {
limitOrder := exchange.GetLimitOrder()
left := limitOrder.GetLeftAsset()
right := limitOrder.GetRightAsset()
price := Truncate(limitOrder.GetPrice())
price := limitOrder.GetPrice()
amount := limitOrder.GetAmount()
op := limitOrder.GetOp()
if !CheckExchangeAsset(left, right) {
......
......@@ -105,7 +105,7 @@ func TestExchange(t *testing.T) {
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyA)
t.Log(tx)
//t.Log(tx)
assert.Nil(t, err)
exec := newExchange()
e := exec.(*exchange)
......@@ -317,7 +317,7 @@ func TestExchange(t *testing.T) {
//反向测试
// orderlimit bty:CCNY 卖bty
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Price: 0.5, Amount: 10 * types.Coin, Op: et.OpSell})
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Price: 50000000, Amount: 10 * types.Coin, Op: et.OpSell})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -365,7 +365,7 @@ func TestExchange(t *testing.T) {
t.Log(reply)
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Price: 0.5, Amount: 10 * types.Coin, Op: et.OpSell})
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Price: 50000000, Amount: 10 * types.Coin, Op: et.OpSell})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -434,7 +434,7 @@ func TestExchange(t *testing.T) {
assert.Equal(t, orderID4, reply2.List[0].OrderID)
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Price: 0.5, Amount: 20 * types.Coin, Op: et.OpBuy})
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Price: 50000000, Amount: 20 * types.Coin, Op: et.OpBuy})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -504,7 +504,7 @@ func TestExchange(t *testing.T) {
// orderlimit bty:CCNY ,先挂买单,然后低于市场价格卖出
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 4, Amount: 5 * types.Coin, Op: et.OpBuy})
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 400000000, Amount: 5 * types.Coin, Op: et.OpBuy})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -555,7 +555,7 @@ func TestExchange(t *testing.T) {
}
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 3, Amount: 10 * types.Coin, Op: et.OpSell})
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 300000000, Amount: 10 * types.Coin, Op: et.OpSell})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -638,7 +638,7 @@ func TestExchange(t *testing.T) {
// orderlimit bty:CCNY ,先挂卖单,然后高于市场价格买入, 买家获利原则
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 4, Amount: 5 * types.Coin, Op: et.OpSell})
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 400000000, Amount: 5 * types.Coin, Op: et.OpSell})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -678,7 +678,7 @@ func TestExchange(t *testing.T) {
orderID8 := orderList.List[0].OrderID
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 5, Amount: 5 * types.Coin, Op: et.OpSell})
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 500000000, Amount: 5 * types.Coin, Op: et.OpSell})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -717,7 +717,7 @@ func TestExchange(t *testing.T) {
orderList = msg.(*et.OrderList)
orderID9 := orderList.List[0].OrderID
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 4.5, Amount: 15 * types.Coin, Op: et.OpBuy})
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 450000000, Amount: 15 * types.Coin, Op: et.OpBuy})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
......@@ -812,22 +812,11 @@ func signTx(tx *types.Transaction, hexPrivKey string) (*types.Transaction, error
return tx, nil
}
func TestTruncate(t *testing.T) {
a := float64(1.00000212000000000001)
b := float64(0.34567)
c := float64(1234567)
t.Log(Truncate(a))
t.Log(Truncate(b))
t.Log(Truncate(c))
t.Log(float64(1.00000212000000000001))
t.Logf("%f", Truncate(float64(1e8)))
t.Log(Truncate(float64(1e-8)))
}
func TestCheckPrice(t *testing.T) {
t.Log(CheckPrice(Truncate(float64(1e8))))
t.Log(CheckPrice(Truncate(float64(1e-8))))
t.Log(CheckPrice(Truncate(float64(1e-9))))
t.Log(CheckPrice(1e8))
t.Log(CheckPrice(-1))
t.Log(CheckPrice(1e17))
t.Log(CheckPrice(0))
}
func TestRawMeta(t *testing.T) {
......@@ -843,3 +832,9 @@ func TestKV(t *testing.T) {
a := &types.KeyValue{Key: []byte("1111111"), Value: nil}
t.Log(a.Key, a.Value)
}
func TestSafeMul(t *testing.T) {
t.Log(SafeMul(1e8, 1e7))
t.Log(SafeMul(1e10, 1e16))
t.Log(SafeMul(1e7, 1e6))
}
......@@ -2,7 +2,7 @@ package executor
import (
"fmt"
"math"
"math/big"
"github.com/33cn/chain33/account"
"github.com/33cn/chain33/client"
......@@ -59,16 +59,16 @@ func (a *Action) OpSwap(op int32) int32 {
}
//计算实际花费
func (a *Action) calcActualCost(op int32, amount int64, price float64) int64 {
func (a *Action) calcActualCost(op int32, amount int64, price int64) int64 {
if op == et.OpBuy {
return int64(float64(amount) * Truncate(price))
return SafeMul(amount, price)
}
return amount
}
//price 精度允许范围小数点后面8位数,0<price<1e8
func CheckPrice(price float64) bool {
if (Truncate(price) >= 1e8) || (Truncate(price)*float64(1e8) < 1) {
//price 精度允许范围 1<=price<=1e16 整数
func CheckPrice(price int64) bool {
if price > 1e16 || price < 1 {
return false
}
return true
......@@ -145,7 +145,7 @@ func (a *Action) LimitOrder(payload *et.LimitOrder) (*types.Receipt, error) {
}
//先检查账户余额
if payload.GetOp() == et.OpBuy {
amount := int64(float64(payload.GetAmount()) * Truncate(payload.GetPrice()))
amount := SafeMul(payload.GetAmount(), payload.GetPrice())
rightAccount := rightAssetDB.LoadExecAccount(a.fromaddr, a.execaddr)
if rightAccount.Balance < amount {
elog.Error("LimitOrder.BalanceCheck", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", amount, "err", et.ErrAssetBalance.Error())
......@@ -260,6 +260,7 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
Value: &et.Order_LimitOrder{LimitOrder: payload},
Ty: et.TyLimitOrderAction,
Executed: 0,
AVGPrice: 0,
Balance: payload.GetAmount(),
Status: et.Ordered,
Addr: a.fromaddr,
......@@ -274,16 +275,17 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
//单笔交易最多撮合100笔历史订单,最大可撮合得深度,系统得自我防护
//迭代已有挂单价格
for {
if count > et.MaxCount {
//当撮合深度大于最大深度时跳出
if count > et.MaxMatchCount {
break
}
//获取现有市场挂单价格信息
marketDepthList, err := QueryMarketDepth(a.localDB, payload.GetLeftAsset(), payload.GetRightAsset(), a.OpSwap(payload.Op), priceKey, et.Count)
if err == types.ErrNotFound {
break
}
//elog.Info("matchLimitOrder.QueryMarketDepth", "marketList", marketDepthList)
for _, marketDepth := range marketDepthList.List {
if count > et.MaxCount {
if count > et.MaxMatchCount {
break
}
// 卖单价大于买单价
......@@ -296,162 +298,43 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
}
//根据价格进行迭代
for {
//当撮合深度大于最大深度时跳出
if count > et.MaxMatchCount {
break
}
orderList, err := findOrderIDListByPrice(a.localDB, payload.GetLeftAsset(), payload.GetRightAsset(), marketDepth.Price, a.OpSwap(payload.Op), et.ListASC, orderKey)
if err == types.ErrNotFound {
break
}
for _, matchorder := range orderList.List {
//当撮合深度大于最大深度时跳出
if count > et.MaxMatchCount {
break
}
//同地址不能交易
if matchorder.Addr == a.fromaddr {
continue
}
//TODO 这里得逻辑是否需要调整?当匹配的单数过多,会导致receipt日志数量激增,理论上存在日志存储攻击,需要加下最大匹配深度,防止这种攻击发生
if matchorder.GetBalance() >= or.GetBalance() {
if payload.Op == et.OpSell {
//转移冻结资产
receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//解冻多余资金
if payload.Price < matchorder.GetLimitOrder().Price {
receipt, err := rightAccountDB.ExecActive(matchorder.Addr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecActive", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
//将达成交易的相应资产结算
receipt, err = leftAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, or.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, or.GetBalance(), payload.Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
if payload.Op == et.OpBuy {
//转移冻结资产
receipt, err := leftAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//将达成交易的相应资产结算
receipt, err = rightAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, or.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, or.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
// match receiptorder,涉及赋值先手顺序,代码顺序不可变
matchorder.Status = func(a, b int64) int32 {
if a > b {
return et.Ordered
}
return et.Completed
}(matchorder.GetBalance(), or.GetBalance())
matchorder.Balance = matchorder.GetBalance() - or.GetBalance()
//记录本次成交得量
matchorder.Executed = or.GetBalance()
a.updateStateDBCache(matchorder)
kvs = append(kvs, a.GetKVSet(matchorder)...)
or.Executed = or.Executed + or.GetBalance()
or.Status = et.Completed
or.Balance = 0
//update receipt order
re.Order = or
re.MatchOrders = append(re.MatchOrders, matchorder)
a.updateStateDBCache(or)
kvs = append(kvs, a.GetKVSet(or)...)
//statedb 更新
//撮合,指针传递
log, kv, err := a.matchModel(leftAccountDB, rightAccountDB, payload, matchorder, or, re)
if err != nil {
return nil, err
}
logs = append(logs, log...)
kvs = append(kvs, kv...)
//订单完成,直接返回,如果没有完成,则继续撮合,直到count等于
if or.Status == et.Completed {
receiptlog := &types.ReceiptLog{Ty: et.TyLimitOrderLog, Log: types.Encode(re)}
logs = append(logs, receiptlog)
receipts := &types.Receipt{Ty: types.ExecOk, KV: kvs, Logs: logs}
return receipts, nil
}
if payload.Op == et.OpSell {
//转移冻结资产
receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//解冻成交部分 多余资金
if payload.Price < matchorder.GetLimitOrder().Price {
receipt, err := rightAccountDB.ExecActive(matchorder.Addr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecActive", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
//将达成交易的相应资产结算
receipt, err = leftAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, matchorder.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, matchorder.GetBalance(), payload.Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
if payload.Op == et.OpBuy {
//转移冻结资产
receipt, err := leftAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//将达成交易的相应资产结算
receipt, err = rightAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
//涉及赋值先后顺序,不可颠倒
or.Balance = or.Balance - matchorder.Balance
or.Executed = or.Executed + matchorder.Balance
or.Status = et.Ordered
a.updateStateDBCache(or)
// match receiptorder
matchorder.Executed = matchorder.Balance
matchorder.Status = et.Completed
matchorder.Balance = 0
a.updateStateDBCache(matchorder)
kvs = append(kvs, a.GetKVSet(matchorder)...)
re.Order = or
re.MatchOrders = append(re.MatchOrders, matchorder)
//TODO 这里得逻辑是否需要调整?当匹配的单数过多,会导致receipt日志数量激增,理论上存在日志存储攻击,需要加下最大匹配深度,防止这种攻击发生
//撮合深度计数
count = count + 1
}
//查询数据不满足5条说明没有了,跳出循环
//查询数据不满足10条说明没有了,跳出循环
if orderList.PrimaryKey == "" {
break
}
......@@ -459,14 +342,14 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
}
}
//查询数据不满足5条说明没有了,跳出循环
//查询的数据如果没有primaryKey说明没有后续数据了,跳出循环
if marketDepthList.PrimaryKey == "" {
break
}
priceKey = marketDepthList.PrimaryKey
}
//冻结剩余未成交的资金
//未完成的订单需要冻结剩余未成交的资金
if payload.Op == et.OpBuy {
receipt, err := rightAccountDB.ExecFrozen(a.fromaddr, a.execaddr, a.calcActualCost(et.OpBuy, or.Balance, payload.Price))
if err != nil {
......@@ -493,6 +376,173 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
return receipts, nil
}
//交易撮合模型
func (a *Action) matchModel(leftAccountDB, rightAccountDB *account.DB, payload *et.LimitOrder, matchorder *et.Order, or *et.Order, re *et.ReceiptExchange) ([]*types.ReceiptLog, []*types.KeyValue, error) {
var logs []*types.ReceiptLog
var kvs []*types.KeyValue
//TODO 这里得逻辑是否需要调整?当匹配的单数过多,会导致receipt日志数量激增,理论上存在日志存储攻击,需要加下最大匹配深度,防止这种攻击发生
//先判断挂单得额度够不够,只有两种状态,大于等于,或者小于
if matchorder.GetBalance() >= or.GetBalance() {
if payload.Op == et.OpSell {
//转移冻结资产
receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//解冻多余资金
if payload.Price < matchorder.GetLimitOrder().Price {
receipt, err := rightAccountDB.ExecActive(matchorder.Addr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecActive", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
//将达成交易的相应资产结算
receipt, err = leftAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, or.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, or.GetBalance(), payload.Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//卖单成交得平均价格始终与自身挂单价格相同
or.AVGPrice = payload.Price
//计算matchOrder平均成交价格
matchorder.AVGPrice = caclAVGPrice(matchorder, payload.Price, payload.Amount)
}
if payload.Op == et.OpBuy {
//转移冻结资产
receipt, err := leftAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//将达成交易的相应资产结算
receipt, err = rightAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, or.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, or.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//买单得话,价格选取卖单的价格
or.AVGPrice = matchorder.GetLimitOrder().Price
//计算matchOrder平均成交价格
matchorder.AVGPrice = caclAVGPrice(matchorder, matchorder.GetLimitOrder().Price, payload.Amount)
}
// match receiptorder,涉及赋值先手顺序,代码顺序不可变
matchorder.Status = func(a, b int64) int32 {
if a > b {
return et.Ordered
}
return et.Completed
}(matchorder.GetBalance(), or.GetBalance())
matchorder.Balance = matchorder.GetBalance() - or.GetBalance()
//记录本次成交得量
matchorder.Executed = or.GetBalance()
a.updateStateDBCache(matchorder)
kvs = append(kvs, a.GetKVSet(matchorder)...)
or.Executed = or.Executed + or.GetBalance()
or.Status = et.Completed
or.Balance = 0
//update receipt order
re.Order = or
re.MatchOrders = append(re.MatchOrders, matchorder)
a.updateStateDBCache(or)
kvs = append(kvs, a.GetKVSet(or)...)
return logs, kvs, nil
}
if payload.Op == et.OpSell {
//转移冻结资产
receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//解冻成交部分 多余资金
if payload.Price < matchorder.GetLimitOrder().Price {
receipt, err := rightAccountDB.ExecActive(matchorder.Addr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecActive", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), matchorder.GetLimitOrder().Price-payload.Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
}
//将达成交易的相应资产结算
receipt, err = leftAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, matchorder.GetBalance(), payload.Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, matchorder.GetBalance(), payload.Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//买单得话,价格选取卖单的价格
or.AVGPrice = payload.Price
//计算matchOrder平均成交价格
matchorder.AVGPrice = caclAVGPrice(matchorder, payload.Price, matchorder.GetBalance())
}
if payload.Op == et.OpBuy {
//转移冻结资产
receipt, err := leftAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//将达成交易的相应资产结算
receipt, err = rightAccountDB.ExecTransfer(a.fromaddr, matchorder.Addr, a.execaddr, a.calcActualCost(payload.Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price), "err", err.Error())
return nil, nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
//买单得话,价格选取卖单的价格
or.AVGPrice = matchorder.GetLimitOrder().Price
//计算matchOrder平均成交价格
matchorder.AVGPrice = caclAVGPrice(matchorder, matchorder.GetLimitOrder().Price, matchorder.GetBalance())
}
//涉及赋值先后顺序,不可颠倒
or.Balance = or.Balance - matchorder.Balance
or.Executed = or.Executed + matchorder.Balance
or.Status = et.Ordered
a.updateStateDBCache(or)
// match receiptorder
matchorder.Executed = matchorder.Balance
matchorder.Status = et.Completed
matchorder.Balance = 0
a.updateStateDBCache(matchorder)
kvs = append(kvs, a.GetKVSet(matchorder)...)
re.Order = or
re.MatchOrders = append(re.MatchOrders, matchorder)
return logs, kvs, nil
}
//根据订单号查询,分为两步,优先去localdb中查询,如没有则再去状态数据库中查询
// 1.挂单中得订单信会根据orderID在localdb中存储
// 2.订单撤销,或者成交后,根据orderID在localdb中存储得数据会被删除,这时只能到状态数据库中查询
......@@ -518,9 +568,9 @@ func findOrderByOrderID(statedb dbm.KV, localdb dbm.KV, orderID int64) (*et.Orde
}
func findOrderIDListByPrice(localdb dbm.KV, left, right *et.Asset, price float64, op, direction int32, primaryKey string) (*et.OrderList, error) {
func findOrderIDListByPrice(localdb dbm.KV, left, right *et.Asset, price int64, op, direction int32, primaryKey string) (*et.OrderList, error) {
table := NewMarketOrderTable(localdb)
prefix := []byte(fmt.Sprintf("%s:%s:%d:%016d", left.GetSymbol(), right.GetSymbol(), op, int64(Truncate(price*float64(1e8)))))
prefix := []byte(fmt.Sprintf("%s:%s:%d:%016d", left.GetSymbol(), right.GetSymbol(), op, price))
var rows []*tab.Row
var err error
......@@ -555,7 +605,7 @@ func Direction(op int32) int32 {
return et.ListASC
}
//这里primaryKey当作主键索引来用,首次查询不需要填值
//这里primaryKey当作主键索引来用,首次查询不需要填值,买单按价格由高往低,卖单按价格由低往高查询
func QueryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, primaryKey string, count int32) (*et.MarketDepthList, error) {
table := NewMarketDepthTable(localdb)
prefix := []byte(fmt.Sprintf("%s:%s:%d", left.GetSymbol(), right.GetSymbol(), op))
......@@ -585,7 +635,7 @@ func QueryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, primaryKe
return &list, nil
}
//QueryHistoryOrderList
//QueryHistoryOrderList 只返回成交的订单信息
func QueryHistoryOrderList(localdb dbm.KV, left, right *et.Asset, primaryKey string, count, direction int32) (types.Message, error) {
table := NewHistoryOrderTable(localdb)
prefix := []byte(fmt.Sprintf("%s:%s", left.Symbol, right.Symbol))
......@@ -632,7 +682,7 @@ HERE:
}
//QueryOrderList,默认展示最新的
func QueryOrderList(localdb dbm.KV, statedb dbm.KV, addr string, status, count, direction int32, primaryKey string) (types.Message, error) {
func QueryOrderList(localdb dbm.KV, addr string, status, count, direction int32, primaryKey string) (types.Message, error) {
var table *tab.Table
if status == et.Completed || status == et.Revoked {
table = NewHistoryOrderTable(localdb)
......@@ -669,9 +719,9 @@ func QueryOrderList(localdb dbm.KV, statedb dbm.KV, addr string, status, count,
return &orderList, nil
}
func queryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, price float64) (*et.MarketDepth, error) {
func queryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, price int64) (*et.MarketDepth, error) {
table := NewMarketDepthTable(localdb)
primaryKey := []byte(fmt.Sprintf("%s:%s:%d:%016d", left.GetSymbol(), right.GetSymbol(), op, int64(Truncate(price)*float64(1e8))))
primaryKey := []byte(fmt.Sprintf("%s:%s:%d:%016d", left.GetSymbol(), right.GetSymbol(), op, price))
row, err := table.GetData(primaryKey)
if err != nil {
return nil, err
......@@ -679,7 +729,19 @@ func queryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, price flo
return row.Data.(*et.MarketDepth), nil
}
//截取小数点后8位
func Truncate(price float64) float64 {
return math.Trunc(float64(1e8)*price) / float64(1e8)
//math库中的安全大数乘法,防溢出
func SafeMul(x, y int64) int64 {
res := big.NewInt(0).Mul(big.NewInt(x), big.NewInt(y))
res = big.NewInt(0).Div(res, big.NewInt(types.Coin))
return res.Int64()
}
//计算平均成交价格
func caclAVGPrice(order *et.Order, price int64, amount int64) int64 {
x := big.NewInt(0).Mul(big.NewInt(order.AVGPrice), big.NewInt(order.GetLimitOrder().Amount-order.GetBalance()))
y := big.NewInt(0).Mul(big.NewInt(price), big.NewInt(amount))
total := big.NewInt(0).Add(x, y)
div := big.NewInt(0).Add(big.NewInt(order.GetLimitOrder().Amount-order.GetBalance()), big.NewInt(amount))
avg := big.NewInt(0).Div(total, div)
return avg.Int64()
}
package executor
import (
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
ety "github.com/33cn/plugin/plugin/dapp/exchange/types"
)
......@@ -77,12 +78,60 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
orderTable := NewMarketOrderTable(e.GetLocalDB())
switch receipt.Order.Status {
case ety.Ordered:
left := receipt.GetOrder().GetLimitOrder().GetLeftAsset()
right := receipt.GetOrder().GetLimitOrder().GetRightAsset()
op := receipt.GetOrder().GetLimitOrder().GetOp()
price := receipt.GetOrder().GetLimitOrder().GetPrice()
order := receipt.GetOrder()
index := receipt.GetIndex()
err := e.updateOrder(marketTable, orderTable, historyTable, receipt.GetOrder(), receipt.GetIndex())
if err != nil {
return nil
}
err = e.updateMatchOrders(marketTable, orderTable, historyTable, receipt.GetOrder(), receipt.GetMatchOrders(), receipt.GetIndex())
if err != nil {
return nil
}
case ety.Completed:
err := e.updateOrder(marketTable, orderTable, historyTable, receipt.GetOrder(), receipt.GetIndex())
if err != nil {
return nil
}
err = e.updateMatchOrders(marketTable, orderTable, historyTable, receipt.GetOrder(), receipt.GetMatchOrders(), receipt.GetIndex())
if err != nil {
return nil
}
case ety.Revoked:
err := e.updateOrder(marketTable, orderTable, historyTable, receipt.GetOrder(), receipt.GetIndex())
if err != nil {
return nil
}
}
//刷新KV
kv, err := marketTable.Save()
if err != nil {
elog.Error("updateIndex", "marketTable.Save", err.Error())
return nil
}
kvs = append(kvs, kv...)
kv, err = orderTable.Save()
if err != nil {
elog.Error("updateIndex", "orderTable.Save", err.Error())
return nil
}
kvs = append(kvs, kv...)
kv, err = historyTable.Save()
if err != nil {
elog.Error("updateIndex", "historyTable.Save", err.Error())
return nil
}
kvs = append(kvs, kv...)
return
}
func (e *exchange) updateOrder(marketTable, orderTable, historyTable *table.Table, order *ety.Order, index int64) error {
left := order.GetLimitOrder().GetLeftAsset()
right := order.GetLimitOrder().GetRightAsset()
op := order.GetLimitOrder().GetOp()
price := order.GetLimitOrder().GetPrice()
switch order.Status {
case ety.Ordered:
var markDepth ety.MarketDepth
depth, err := queryMarketDepth(e.GetLocalDB(), left, right, op, price)
if err == types.ErrNotFound {
......@@ -90,169 +139,34 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
markDepth.LeftAsset = left
markDepth.RightAsset = right
markDepth.Op = op
markDepth.Amount = receipt.Order.Balance
markDepth.Amount = order.Balance
} else {
markDepth.Price = price
markDepth.LeftAsset = left
markDepth.RightAsset = right
markDepth.Op = op
markDepth.Amount = depth.Amount + receipt.Order.Balance
markDepth.Amount = depth.Amount + order.Balance
}
//marketDepth
err = marketTable.Replace(&markDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.Replace", err.Error())
return nil
return err
}
err = orderTable.Replace(order)
if err != nil {
elog.Error("updateIndex", "orderTable.Replace", err.Error())
return nil
return err
}
if len(receipt.MatchOrders) > 0 {
//撮合交易更新
cache := make(map[float64]int64)
for i, matchOrder := range receipt.MatchOrders {
if matchOrder.Status == ety.Completed {
// 删除原有状态orderID
matchOrder.Status = ety.Ordered
err = orderTable.DelRow(matchOrder)
if err != nil {
elog.Error("updateIndex", "orderTable.DelRow", err.Error())
return nil
}
//索引index,改为当前的index
matchOrder.Status = ety.Completed
matchOrder.Index = index + int64(i+1)
err = historyTable.Replace(matchOrder)
if err != nil {
elog.Error("updateIndex", "historyTable.Replace", err.Error())
return nil
}
}
if matchOrder.Status == ety.Ordered {
//更新数据
err = orderTable.Replace(matchOrder)
if err != nil {
elog.Error("updateIndex", "orderTable.Replace", err.Error())
return nil
}
}
executed := cache[matchOrder.GetLimitOrder().Price]
executed = executed + matchOrder.Executed
cache[matchOrder.GetLimitOrder().Price] = executed
}
//更改匹配市场深度
for pr, executed := range cache {
var matchDepth ety.MarketDepth
depth, err := queryMarketDepth(e.GetLocalDB(), left, right, OpSwap(op), pr)
if err == types.ErrNotFound {
continue
} else {
matchDepth.Price = pr
matchDepth.LeftAsset = left
matchDepth.RightAsset = right
matchDepth.Op = OpSwap(op)
matchDepth.Amount = depth.Amount - executed
}
//marketDepth
err = marketTable.Replace(&matchDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.Replace", err.Error())
return nil
}
if matchDepth.Amount <= 0 {
//删除
err = marketTable.DelRow(&matchDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.DelRow", err.Error())
return nil
}
}
}
}
case ety.Completed:
left := receipt.GetOrder().GetLimitOrder().GetLeftAsset()
right := receipt.GetOrder().GetLimitOrder().GetRightAsset()
op := receipt.GetOrder().GetLimitOrder().GetOp()
index := receipt.GetIndex()
err := historyTable.Replace(receipt.GetOrder())
err := historyTable.Replace(order)
if err != nil {
elog.Error("updateIndex", "historyTable.Replace", err.Error())
return nil
}
cache := make(map[float64]int64)
if len(receipt.MatchOrders) > 0 {
//撮合交易更新
for i, matchOrder := range receipt.MatchOrders {
if matchOrder.Status == ety.Completed {
// 删除原有状态orderID
matchOrder.Status = ety.Ordered
err = orderTable.DelRow(matchOrder)
if err != nil {
elog.Error("updateIndex", "orderTable.DelRow", err.Error())
return nil
}
//索引index,改为当前的index
matchOrder.Status = ety.Completed
matchOrder.Index = index + int64(i+1)
err = historyTable.Replace(matchOrder)
if err != nil {
elog.Error("updateIndex", "historyTable.Replace", err.Error())
return nil
}
}
if matchOrder.Status == ety.Ordered {
//更新数据
err = orderTable.Replace(matchOrder)
if err != nil {
elog.Error("updateIndex", "orderTable.Replace", err.Error())
return nil
}
}
executed := cache[matchOrder.GetLimitOrder().Price]
executed = executed + matchOrder.Executed
cache[matchOrder.GetLimitOrder().Price] = executed
}
//更改match市场深度
for pr, executed := range cache {
var matchDepth ety.MarketDepth
depth, err := queryMarketDepth(e.GetLocalDB(), left, right, OpSwap(op), pr)
if err == types.ErrNotFound {
continue
} else {
matchDepth.Price = pr
matchDepth.LeftAsset = left
matchDepth.RightAsset = right
matchDepth.Op = OpSwap(op)
matchDepth.Amount = depth.Amount - executed
}
//marketDepth
err = marketTable.Replace(&matchDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.Replace", err.Error())
return nil
}
if matchDepth.Amount <= 0 {
//删除
err = marketTable.DelRow(&matchDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.DelRow", err.Error())
return nil
}
}
}
return err
}
case ety.Revoked:
//只有状态时ordered状态的订单才能被撤回
left := receipt.GetOrder().GetLimitOrder().GetLeftAsset()
right := receipt.GetOrder().GetLimitOrder().GetRightAsset()
op := receipt.GetOrder().GetLimitOrder().GetOp()
price := receipt.GetOrder().GetLimitOrder().GetPrice()
order := receipt.GetOrder()
index := receipt.GetIndex()
var marketDepth ety.MarketDepth
depth, err := queryMarketDepth(e.GetLocalDB(), left, right, op, price)
if err == nil {
......@@ -265,7 +179,7 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
err = marketTable.Replace(&marketDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.Replace", err.Error())
return nil
return err
}
}
if marketDepth.Amount <= 0 {
......@@ -273,7 +187,7 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
err = marketTable.DelRow(&marketDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.DelRow", err.Error())
return nil
return err
}
}
//删除原有状态orderID
......@@ -281,7 +195,7 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
err = orderTable.DelRow(order)
if err != nil {
elog.Error("updateIndex", "orderTable.DelRow", err.Error())
return nil
return err
}
order.Status = ety.Revoked
order.Index = index
......@@ -289,32 +203,80 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
err = historyTable.Replace(order)
if err != nil {
elog.Error("updateIndex", "historyTable.Replace", err.Error())
return nil
return err
}
}
return nil
}
func (e *exchange) updateMatchOrders(marketTable, orderTable, historyTable *table.Table, order *ety.Order, matchOrders []*ety.Order, index int64) error {
left := order.GetLimitOrder().GetLeftAsset()
right := order.GetLimitOrder().GetRightAsset()
op := order.GetLimitOrder().GetOp()
if len(matchOrders) > 0 {
//撮合交易更新
cache := make(map[int64]int64)
for i, matchOrder := range matchOrders {
if matchOrder.Status == ety.Completed {
// 删除原有状态orderID
matchOrder.Status = ety.Ordered
err := orderTable.DelRow(matchOrder)
if err != nil {
elog.Error("updateIndex", "orderTable.DelRow", err.Error())
return err
}
//索引index,改为当前的index
matchOrder.Status = ety.Completed
matchOrder.Index = index + int64(i+1)
err = historyTable.Replace(matchOrder)
if err != nil {
elog.Error("updateIndex", "historyTable.Replace", err.Error())
return err
}
}
if matchOrder.Status == ety.Ordered {
//更新数据
err := orderTable.Replace(matchOrder)
if err != nil {
elog.Error("updateIndex", "orderTable.Replace", err.Error())
return err
}
}
executed := cache[matchOrder.GetLimitOrder().Price]
executed = executed + matchOrder.Executed
cache[matchOrder.GetLimitOrder().Price] = executed
}
kv, err := marketTable.Save()
if err != nil {
elog.Error("updateIndex", "marketTable.Save", err.Error())
return nil
}
kvs = append(kvs, kv...)
kv, err = orderTable.Save()
if err != nil {
elog.Error("updateIndex", "orderTable.Save", err.Error())
return nil
}
kvs = append(kvs, kv...)
kv, err = historyTable.Save()
if err != nil {
elog.Error("updateIndex", "historyTable.Save", err.Error())
return nil
//更改匹配市场深度
for pr, executed := range cache {
var matchDepth ety.MarketDepth
depth, err := queryMarketDepth(e.GetLocalDB(), left, right, OpSwap(op), pr)
if err == types.ErrNotFound {
continue
} else {
matchDepth.Price = pr
matchDepth.LeftAsset = left
matchDepth.RightAsset = right
matchDepth.Op = OpSwap(op)
matchDepth.Amount = depth.Amount - executed
}
//marketDepth
err = marketTable.Replace(&matchDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.Replace", err.Error())
return err
}
if matchDepth.Amount <= 0 {
//删除
err = marketTable.DelRow(&matchDepth)
if err != nil {
elog.Error("updateIndex", "marketTable.DelRow", err.Error())
return err
}
}
}
}
kvs = append(kvs, kv...)
return
return nil
}
func OpSwap(op int32) int32 {
if op == ety.OpBuy {
return ety.OpSell
......
......@@ -10,9 +10,6 @@ func (s *exchange) Query_QueryMarketDepth(in *et.QueryMarketDepth) (types.Messag
if !CheckCount(in.Count) {
return nil, et.ErrCount
}
if in.Count == 0 {
in.Count = 10
}
if !CheckExchangeAsset(in.LeftAsset, in.RightAsset) {
return nil, et.ErrAsset
}
......@@ -28,7 +25,7 @@ func (s *exchange) Query_QueryHistoryOrderList(in *et.QueryHistoryOrderList) (ty
if !CheckExchangeAsset(in.LeftAsset, in.RightAsset) {
return nil, et.ErrAsset
}
if in.Count > 20 {
if !CheckCount(in.Count) {
return nil, et.ErrCount
}
......@@ -62,5 +59,5 @@ func (s *exchange) Query_QueryOrderList(in *et.QueryOrderList) (types.Message, e
if in.Address == "" {
return nil, et.ErrAddr
}
return QueryOrderList(s.GetLocalDB(), s.GetStateDB(), in.Address, in.Status, in.Count, in.Direction, in.PrimaryKey)
return QueryOrderList(s.GetLocalDB(), in.Address, in.Status, in.Count, in.Direction, in.PrimaryKey)
}
......@@ -107,7 +107,7 @@ func (r *OrderRow) Get(key string) ([]byte, error) {
if key == "orderID" {
return []byte(fmt.Sprintf("%022d", r.OrderID)), nil
} else if key == "market_order" {
return []byte(fmt.Sprintf("%s:%s:%d:%016d", r.GetLimitOrder().LeftAsset.GetSymbol(), r.GetLimitOrder().RightAsset.GetSymbol(), r.GetLimitOrder().Op, int64(Truncate(r.GetLimitOrder().Price*float64(1e8))))), nil
return []byte(fmt.Sprintf("%s:%s:%d:%016d", r.GetLimitOrder().LeftAsset.GetSymbol(), r.GetLimitOrder().RightAsset.GetSymbol(), r.GetLimitOrder().Op, r.GetLimitOrder().Price)), nil
} else if key == "addr_status" {
return []byte(fmt.Sprintf("%s:%d", r.Addr, r.Status)), nil
}
......@@ -175,7 +175,7 @@ func (m *MarketDepthRow) SetPayload(data types.Message) error {
//Get 按照indexName 查询 indexValue
func (m *MarketDepthRow) Get(key string) ([]byte, error) {
if key == "price" {
return []byte(fmt.Sprintf("%s:%s:%d:%016d", m.LeftAsset.GetSymbol(), m.RightAsset.GetSymbol(), m.Op, int64(Truncate(m.Price)*float64(1e8)))), nil
return []byte(fmt.Sprintf("%s:%s:%d:%016d", m.LeftAsset.GetSymbol(), m.RightAsset.GetSymbol(), m.Op, m.Price)), nil
}
return nil, types.ErrNotFound
}
......@@ -19,7 +19,7 @@ message LimitOrder {
//交易对
asset rightAsset = 2;
//价格
double price = 3;
int64 price = 3;
//总量
int64 amount = 4;
//操作, 1为买,2为卖
......@@ -60,16 +60,18 @@ message Order {
int32 ty = 4;
//已经成交的数量
int64 executed = 5;
//成交均价
int64 AVG_price = 6;
//余额
int64 balance = 6;
int64 balance = 7;
//状态,0 挂单中ordered, 1 完成completed, 2撤回 revoked
int32 status = 7;
int32 status = 8;
//用户地址
string addr = 8;
string addr = 9;
//更新时间
int64 updateTime = 9;
int64 updateTime = 10;
//索引
int64 index = 10;
int64 index = 11;
}
//查询接口
......@@ -92,7 +94,7 @@ message MarketDepth {
//资产2
asset rightAsset = 2;
//价格
double price = 3;
int64 price = 3;
//总量
int64 amount = 4;
//操作, 1为买,2为卖
......
......@@ -59,9 +59,9 @@ const (
const (
//单次list还回条数
Count = int32(5)
Count = int32(10)
//系统最大撮合深度
MaxCount = 100
MaxMatchCount = 100
)
var (
......
......@@ -26,15 +26,12 @@ It has these top-level messages:
*/
package types
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
......@@ -224,7 +221,7 @@ type LimitOrder struct {
// 交易对
RightAsset *Asset `protobuf:"bytes,2,opt,name=rightAsset" json:"rightAsset,omitempty"`
// 价格
Price float64 `protobuf:"fixed64,3,opt,name=price" json:"price,omitempty"`
Price int64 `protobuf:"varint,3,opt,name=price" json:"price,omitempty"`
// 总量
Amount int64 `protobuf:"varint,4,opt,name=amount" json:"amount,omitempty"`
// 操作, 1为买,2为卖
......@@ -250,7 +247,7 @@ func (m *LimitOrder) GetRightAsset() *Asset {
return nil
}
func (m *LimitOrder) GetPrice() float64 {
func (m *LimitOrder) GetPrice() int64 {
if m != nil {
return m.Price
}
......@@ -370,16 +367,18 @@ type Order struct {
Ty int32 `protobuf:"varint,4,opt,name=ty" json:"ty,omitempty"`
// 已经成交的数量
Executed int64 `protobuf:"varint,5,opt,name=executed" json:"executed,omitempty"`
// 成交均价
AVGPrice int64 `protobuf:"varint,6,opt,name=AVG_price,json=AVGPrice" json:"AVG_price,omitempty"`
// 余额
Balance int64 `protobuf:"varint,6,opt,name=balance" json:"balance,omitempty"`
Balance int64 `protobuf:"varint,7,opt,name=balance" json:"balance,omitempty"`
// 状态,0 挂单中ordered, 1 完成completed, 2撤回 revoked
Status int32 `protobuf:"varint,7,opt,name=status" json:"status,omitempty"`
Status int32 `protobuf:"varint,8,opt,name=status" json:"status,omitempty"`
// 用户地址
Addr string `protobuf:"bytes,8,opt,name=addr" json:"addr,omitempty"`
Addr string `protobuf:"bytes,9,opt,name=addr" json:"addr,omitempty"`
// 更新时间
UpdateTime int64 `protobuf:"varint,9,opt,name=updateTime" json:"updateTime,omitempty"`
UpdateTime int64 `protobuf:"varint,10,opt,name=updateTime" json:"updateTime,omitempty"`
// 索引
Index int64 `protobuf:"varint,10,opt,name=index" json:"index,omitempty"`
Index int64 `protobuf:"varint,11,opt,name=index" json:"index,omitempty"`
}
func (m *Order) Reset() { *m = Order{} }
......@@ -443,6 +442,13 @@ func (m *Order) GetExecuted() int64 {
return 0
}
func (m *Order) GetAVGPrice() int64 {
if m != nil {
return m.AVGPrice
}
return 0
}
func (m *Order) GetBalance() int64 {
if m != nil {
return m.Balance
......@@ -613,7 +619,7 @@ type MarketDepth struct {
// 资产2
RightAsset *Asset `protobuf:"bytes,2,opt,name=rightAsset" json:"rightAsset,omitempty"`
// 价格
Price float64 `protobuf:"fixed64,3,opt,name=price" json:"price,omitempty"`
Price int64 `protobuf:"varint,3,opt,name=price" json:"price,omitempty"`
// 总量
Amount int64 `protobuf:"varint,4,opt,name=amount" json:"amount,omitempty"`
// 操作, 1为买,2为卖
......@@ -639,7 +645,7 @@ func (m *MarketDepth) GetRightAsset() *Asset {
return nil
}
func (m *MarketDepth) GetPrice() float64 {
func (m *MarketDepth) GetPrice() int64 {
if m != nil {
return m.Price
}
......@@ -927,46 +933,47 @@ var _Exchange_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("exchange.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 644 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xae, 0xed, 0xb8, 0xa9, 0x27, 0x28, 0x85, 0x15, 0x20, 0x0b, 0x21, 0x14, 0xf9, 0x50, 0x2a,
0x84, 0x72, 0x68, 0x25, 0x38, 0x17, 0x15, 0xa9, 0x88, 0x56, 0x88, 0x15, 0x17, 0x8e, 0xae, 0x3d,
0x34, 0xab, 0x26, 0xb1, 0xb5, 0x5e, 0x57, 0xb5, 0x78, 0x08, 0x6e, 0x3c, 0x01, 0xbc, 0x00, 0x2f,
0xc0, 0x89, 0x2b, 0xcf, 0x84, 0x76, 0x76, 0x1d, 0x6f, 0xd2, 0xa2, 0x56, 0xa0, 0xdc, 0xfc, 0xcd,
0x9f, 0xbf, 0x99, 0xf9, 0x76, 0x17, 0x86, 0x78, 0x99, 0x4d, 0xd2, 0xf9, 0x19, 0x8e, 0x4b, 0x59,
0xa8, 0x82, 0x85, 0xaa, 0x29, 0xb1, 0x4a, 0x00, 0xb6, 0x5e, 0x5b, 0x47, 0xf2, 0xdb, 0x83, 0x61,
0x0b, 0x0e, 0x32, 0x25, 0x8a, 0x39, 0xdb, 0x07, 0x98, 0x8a, 0x99, 0x50, 0xef, 0x64, 0x8e, 0x32,
0xf6, 0x46, 0xde, 0xee, 0x60, 0xef, 0xde, 0x98, 0x52, 0xc7, 0xc7, 0x0b, 0xc7, 0xd1, 0x06, 0x77,
0xc2, 0xd8, 0x0b, 0x18, 0xcc, 0x52, 0x79, 0x8e, 0x36, 0xcb, 0xa7, 0x2c, 0x66, 0xb3, 0x4e, 0x3a,
0xcf, 0xd1, 0x06, 0x77, 0x03, 0x75, 0x9e, 0xc4, 0x8b, 0xe2, 0x1c, 0x4d, 0x5e, 0xb0, 0x94, 0xc7,
0x3b, 0x8f, 0xce, 0x73, 0x02, 0xd9, 0x10, 0x7c, 0xd5, 0xc4, 0x9b, 0x23, 0x6f, 0x37, 0xe4, 0xbe,
0x6a, 0x5e, 0xf5, 0x21, 0xbc, 0x48, 0xa7, 0x35, 0x26, 0xdf, 0x3c, 0x80, 0x8e, 0x25, 0x7b, 0x06,
0xd1, 0x14, 0x3f, 0xa9, 0x83, 0xaa, 0x42, 0x65, 0x7b, 0xb9, 0x63, 0xab, 0xa7, 0xda, 0xc6, 0x3b,
0x37, 0x7b, 0x0e, 0x20, 0xc5, 0xd9, 0xc4, 0x06, 0xfb, 0xd7, 0x04, 0x3b, 0x7e, 0x76, 0x1f, 0xc2,
0x52, 0x8a, 0x0c, 0x89, 0xb3, 0xc7, 0x0d, 0x60, 0x0f, 0x61, 0x33, 0x9d, 0x15, 0xf5, 0x5c, 0xc5,
0xbd, 0x91, 0xb7, 0x1b, 0x70, 0x8b, 0x34, 0xdf, 0xa2, 0x8c, 0x43, 0xc3, 0xb7, 0x28, 0x93, 0x2f,
0x1e, 0x0c, 0x9c, 0xb1, 0xac, 0x91, 0x67, 0xc7, 0x28, 0xb8, 0x86, 0x51, 0x6f, 0xc1, 0xe8, 0x29,
0x0c, 0x9c, 0x79, 0xb3, 0x18, 0xfa, 0x85, 0xfe, 0x78, 0x73, 0x48, 0x74, 0x02, 0xde, 0xc2, 0xe4,
0x25, 0x84, 0x69, 0x5b, 0x19, 0x2f, 0x31, 0xb3, 0x22, 0x89, 0xb8, 0x45, 0xda, 0x5e, 0x35, 0xb3,
0xd3, 0x62, 0x4a, 0xdc, 0x22, 0x6e, 0x51, 0xf2, 0xd3, 0x87, 0xf0, 0x86, 0xe2, 0x2b, 0xe2, 0xf3,
0xff, 0x49, 0x7c, 0xc1, 0x6d, 0xc5, 0x67, 0x44, 0xd4, 0x6b, 0x45, 0xc4, 0x1e, 0xc1, 0x96, 0x6e,
0xa1, 0x56, 0x98, 0xd3, 0xaa, 0x02, 0xbe, 0xc0, 0x9a, 0xf2, 0x69, 0x3a, 0x4d, 0xe7, 0x19, 0x92,
0xea, 0x02, 0xde, 0x42, 0x6a, 0x57, 0xa5, 0xaa, 0xae, 0xe2, 0x3e, 0x55, 0xb2, 0x88, 0x31, 0xe8,
0xa5, 0x79, 0x2e, 0xe3, 0x2d, 0x1a, 0x02, 0x7d, 0xb3, 0x27, 0x00, 0x75, 0x99, 0xa7, 0x0a, 0x3f,
0x88, 0x19, 0xc6, 0x11, 0x15, 0x72, 0x2c, 0x5a, 0x54, 0x62, 0x9e, 0xe3, 0x65, 0x0c, 0xe4, 0x32,
0xa0, 0x13, 0xf7, 0x0f, 0x0f, 0xee, 0xbe, 0xaf, 0x51, 0x36, 0xa6, 0xa9, 0x43, 0x2c, 0xd5, 0x64,
0x8d, 0xd2, 0x31, 0x12, 0x09, 0x5a, 0x89, 0x68, 0xf6, 0xa5, 0x14, 0xb3, 0x54, 0x36, 0x6f, 0xd1,
0xcc, 0x2d, 0xe2, 0x8e, 0x45, 0xb3, 0xcf, 0x48, 0x69, 0x46, 0xe7, 0x06, 0x24, 0xdf, 0x17, 0x52,
0x5f, 0x37, 0xdf, 0xff, 0x3b, 0x92, 0x1f, 0x61, 0xdb, 0xa1, 0x79, 0x2c, 0x2a, 0xc5, 0x76, 0xa0,
0x37, 0x15, 0x95, 0x66, 0x19, 0x5c, 0x51, 0x14, 0x45, 0x71, 0xf2, 0xaf, 0x0c, 0xc6, 0x5f, 0x1d,
0x4c, 0xf2, 0xcb, 0x83, 0x07, 0xb4, 0xb7, 0x23, 0x51, 0xa9, 0x42, 0x36, 0x24, 0x3f, 0xfa, 0xc3,
0xfa, 0x86, 0xb1, 0xcc, 0x29, 0xf8, 0xfb, 0xb2, 0x7a, 0xce, 0xb2, 0xd8, 0x63, 0x88, 0x72, 0x21,
0x91, 0x5e, 0x02, 0x3b, 0x9b, 0xce, 0x90, 0xec, 0x00, 0x50, 0x1b, 0x37, 0x5d, 0x11, 0x5f, 0x3d,
0x18, 0x76, 0x81, 0xd4, 0x68, 0x77, 0x4a, 0xbc, 0xa5, 0x53, 0x12, 0x43, 0x5f, 0x9f, 0x0c, 0xac,
0x2a, 0x3b, 0xb7, 0x16, 0xae, 0xa5, 0x81, 0x13, 0x88, 0x3a, 0x4a, 0xa3, 0xa5, 0xed, 0xb6, 0x93,
0x24, 0xff, 0x2d, 0xf7, 0xfa, 0x19, 0xb6, 0x39, 0x66, 0x28, 0x4a, 0xd5, 0xbe, 0xa1, 0x2c, 0x81,
0xb0, 0x70, 0x1e, 0xce, 0xe5, 0xaa, 0xc6, 0xc5, 0xc6, 0xfa, 0xbe, 0x52, 0xd9, 0x84, 0x8c, 0xba,
0xef, 0xab, 0xff, 0x77, 0x03, 0xba, 0x5b, 0x21, 0x70, 0x6e, 0x85, 0x3d, 0xd0, 0xb7, 0x95, 0xf9,
0xeb, 0xe9, 0x26, 0x3d, 0xf0, 0xfb, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x99, 0xd7, 0x24, 0x52,
0xf2, 0x07, 0x00, 0x00,
// 667 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xdd, 0x6e, 0xd3, 0x4a,
0x10, 0xae, 0xed, 0x38, 0xa9, 0x27, 0x47, 0xe9, 0x39, 0xab, 0x73, 0x8e, 0x2c, 0x40, 0xa8, 0xf2,
0x45, 0xa9, 0x10, 0xca, 0x45, 0x2b, 0xc1, 0x75, 0x50, 0x51, 0x8b, 0x68, 0x05, 0xac, 0x50, 0x25,
0xae, 0x90, 0x6b, 0x0f, 0xcd, 0xaa, 0x49, 0x6c, 0xad, 0x37, 0x55, 0x2d, 0x1e, 0x82, 0x3b, 0x9e,
0x00, 0x5e, 0x80, 0x77, 0xe0, 0x96, 0x27, 0xe0, 0x61, 0xd0, 0xce, 0xae, 0xe3, 0x4d, 0x5b, 0xd4,
0x0a, 0x94, 0x3b, 0x7f, 0xf3, 0xb3, 0xfe, 0x66, 0xe6, 0x9b, 0x5d, 0x18, 0xe0, 0x45, 0x36, 0x4e,
0x67, 0xa7, 0x38, 0x2c, 0x65, 0xa1, 0x0a, 0x16, 0xaa, 0xba, 0xc4, 0x2a, 0x01, 0x58, 0x7f, 0x66,
0x1d, 0xc9, 0x77, 0x0f, 0x06, 0x0d, 0x18, 0x65, 0x4a, 0x14, 0x33, 0xb6, 0x0b, 0x30, 0x11, 0x53,
0xa1, 0x5e, 0xca, 0x1c, 0x65, 0xec, 0x6d, 0x7a, 0xdb, 0xfd, 0x9d, 0x7f, 0x86, 0x94, 0x3a, 0x3c,
0x5c, 0x38, 0x0e, 0xd6, 0xb8, 0x13, 0xc6, 0x1e, 0x43, 0x7f, 0x9a, 0xca, 0x33, 0xb4, 0x59, 0x3e,
0x65, 0x31, 0x9b, 0x75, 0xd4, 0x7a, 0x0e, 0xd6, 0xb8, 0x1b, 0xa8, 0xf3, 0x24, 0x9e, 0x17, 0x67,
0x68, 0xf2, 0x82, 0xa5, 0x3c, 0xde, 0x7a, 0x74, 0x9e, 0x13, 0xc8, 0x06, 0xe0, 0xab, 0x3a, 0xee,
0x6e, 0x7a, 0xdb, 0x21, 0xf7, 0x55, 0xfd, 0xb4, 0x07, 0xe1, 0x79, 0x3a, 0x99, 0x63, 0xf2, 0xd9,
0x03, 0x68, 0x59, 0xb2, 0x87, 0x10, 0x4d, 0xf0, 0xbd, 0x1a, 0x55, 0x15, 0x2a, 0x5b, 0xcb, 0x5f,
0xf6, 0xf4, 0x54, 0xdb, 0x78, 0xeb, 0x66, 0x8f, 0x00, 0xa4, 0x38, 0x1d, 0xdb, 0x60, 0xff, 0x9a,
0x60, 0xc7, 0xcf, 0xfe, 0x85, 0xb0, 0x94, 0x22, 0x43, 0xe2, 0x1c, 0x70, 0x03, 0xd8, 0xff, 0xd0,
0x4d, 0xa7, 0xc5, 0x7c, 0xa6, 0xe2, 0x0e, 0x99, 0x2d, 0xd2, 0x7c, 0x8b, 0x32, 0x0e, 0x0d, 0xdf,
0xa2, 0x4c, 0x3e, 0x7a, 0xd0, 0x77, 0xda, 0xb2, 0x42, 0x9e, 0x2d, 0xa3, 0xe0, 0x1a, 0x46, 0x9d,
0x05, 0xa3, 0x07, 0xd0, 0x77, 0xfa, 0xcd, 0x62, 0xe8, 0x15, 0xfa, 0xe3, 0xf9, 0x1e, 0xd1, 0x09,
0x78, 0x03, 0x93, 0x27, 0x10, 0xa6, 0xcd, 0xc9, 0x78, 0x81, 0x99, 0x15, 0x49, 0xc4, 0x2d, 0xd2,
0xf6, 0xaa, 0x9e, 0x9e, 0x14, 0x13, 0xe2, 0x16, 0x71, 0x8b, 0x92, 0x1f, 0x3e, 0x84, 0x37, 0x1c,
0x7e, 0x49, 0x7c, 0xfe, 0x6f, 0x89, 0x2f, 0xb8, 0xad, 0xf8, 0x8c, 0x88, 0x3a, 0x8d, 0x88, 0xd8,
0x1d, 0x58, 0xd7, 0x25, 0xcc, 0x15, 0xe6, 0x34, 0xaa, 0x80, 0x2f, 0x30, 0xbb, 0x0b, 0xd1, 0xe8,
0x78, 0xff, 0x9d, 0x19, 0x79, 0xd7, 0x38, 0x47, 0xc7, 0xfb, 0xaf, 0x68, 0xea, 0x31, 0xf4, 0x4e,
0xd2, 0x49, 0x3a, 0xcb, 0x30, 0xee, 0x99, 0x7a, 0x2c, 0xa4, 0x5e, 0xa8, 0x54, 0xcd, 0xab, 0x78,
0x9d, 0x7e, 0x63, 0x11, 0x63, 0xd0, 0x49, 0xf3, 0x5c, 0xc6, 0x11, 0x75, 0x88, 0xbe, 0xd9, 0x7d,
0x80, 0x79, 0x99, 0xa7, 0x0a, 0xdf, 0x88, 0x29, 0xc6, 0x40, 0x07, 0x39, 0x16, 0xad, 0x38, 0x31,
0xcb, 0xf1, 0x22, 0xee, 0x1b, 0xc5, 0x11, 0x68, 0x95, 0xff, 0xd5, 0x83, 0xbf, 0x5f, 0xcf, 0x51,
0xd6, 0xa6, 0xe2, 0x3d, 0x2c, 0xd5, 0x78, 0x85, 0xba, 0x32, 0xfa, 0x09, 0x1a, 0xfd, 0x68, 0xf6,
0xa5, 0x14, 0xd3, 0x54, 0xd6, 0x2f, 0xd0, 0x34, 0x35, 0xe2, 0x8e, 0x45, 0xb3, 0xcf, 0x48, 0x86,
0x66, 0x09, 0x0c, 0x48, 0xbe, 0x2c, 0xf6, 0x60, 0xd5, 0x7c, 0xff, 0x6c, 0x5f, 0xdf, 0xc2, 0x86,
0x43, 0xf3, 0x50, 0x54, 0x8a, 0x6d, 0x41, 0x67, 0x22, 0x2a, 0xcd, 0x32, 0xb8, 0x22, 0x37, 0x8a,
0xe2, 0xe4, 0xbf, 0xd4, 0x18, 0xff, 0x72, 0x63, 0x92, 0x6f, 0x1e, 0xfc, 0x47, 0x73, 0x3b, 0x10,
0x95, 0x2a, 0x64, 0x4d, 0xda, 0xa4, 0x3f, 0xac, 0xae, 0x19, 0xcb, 0x9c, 0x82, 0x5f, 0x0f, 0xab,
0xe3, 0x0c, 0x8b, 0xdd, 0x83, 0x28, 0x17, 0x12, 0xe9, 0x99, 0xb0, 0xbd, 0x69, 0x0d, 0xc9, 0x16,
0x00, 0x95, 0x71, 0xd3, 0xfd, 0xf1, 0xc9, 0x83, 0x41, 0x1b, 0x48, 0x85, 0xb6, 0x5b, 0xe2, 0x2d,
0x6d, 0x49, 0x0c, 0x3d, 0xbd, 0x19, 0x58, 0x55, 0xb6, 0x6f, 0x0d, 0x5c, 0x49, 0x01, 0x47, 0x10,
0xb5, 0x94, 0x36, 0x97, 0xa6, 0xdb, 0x74, 0x92, 0xfc, 0xb7, 0x9c, 0xeb, 0x07, 0xd8, 0xe0, 0x98,
0xa1, 0x28, 0x55, 0xf3, 0xc0, 0xb2, 0x04, 0xc2, 0xc2, 0x79, 0x55, 0x97, 0x4f, 0x35, 0x2e, 0x36,
0xd4, 0x97, 0x99, 0xca, 0xc6, 0x64, 0xd4, 0x75, 0x5f, 0xfd, 0xbf, 0x1b, 0xd0, 0xde, 0x0a, 0x81,
0x73, 0x2b, 0xec, 0x80, 0xbe, 0xca, 0xcc, 0x5f, 0x4f, 0xba, 0xf4, 0xfa, 0xef, 0xfe, 0x0c, 0x00,
0x00, 0xff, 0xff, 0xf2, 0x3c, 0xac, 0xed, 0x0f, 0x08, 0x00, 0x00,
}
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