Commit 5d8922a2 authored by harrylee's avatar harrylee Committed by vipwzw

add exchange_test.go

parent ef710316
......@@ -2,13 +2,466 @@ package executor
import (
"testing"
"github.com/33cn/chain33/account"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
"github.com/33cn/chain33/client"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/crypto"
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/queue"
et "github.com/33cn/plugin/plugin/dapp/exchange/types"
"github.com/stretchr/testify/assert"
)
type execEnv struct {
blockTime int64
blockHeight int64
difficulty uint64
}
var (
PrivKeyA = "0x6da92a632ab7deb67d38c0f6560bcfed28167998f6496db64c258d5e8393a81b" // 1KSBd17H7ZK8iT37aJztFB22XGwsPTdwE4
PrivKeyB = "0x19c069234f9d3e61135fefbeb7791b149cdf6af536f26bebb310d4cd22c3fee4" // 1JRNjdEqp4LJ5fqycUBm9ayCKSeeskgMKR
PrivKeyC = "0x7a80a1f75d7360c6123c32a78ecf978c1ac55636f87892df38d8b85a9aeff115" // 1NLHPEcbTWWxxU3dGUZBhayjrCHD3psX7k
PrivKeyD = "0xcacb1f5d51700aea07fca2246ab43b0917d70405c65edea9b5063d72eb5c6b71" // 1MCftFynyvG2F4ED5mdHYgziDxx6vDrScs
Nodes = [][]byte{
[]byte("1KSBd17H7ZK8iT37aJztFB22XGwsPTdwE4"),
[]byte("1JRNjdEqp4LJ5fqycUBm9ayCKSeeskgMKR"),
[]byte("1NLHPEcbTWWxxU3dGUZBhayjrCHD3psX7k"),
[]byte("1MCftFynyvG2F4ED5mdHYgziDxx6vDrScs"),
}
)
func TestExchange(t *testing.T) {
cfg := types.NewChain33Config(types.GetDefaultCfgstring())
cfg.SetTitleOnlyForTest("chain33")
Init(et.ExchangeX, cfg, nil)
total := 100 * types.Coin
accountA := types.Account{
Balance: total,
Frozen: 0,
Addr: string(Nodes[0]),
}
accountB := types.Account{
Balance: total,
Frozen: 0,
Addr: string(Nodes[1]),
}
accountC := types.Account{
Balance: total,
Frozen: 0,
Addr: string(Nodes[2]),
}
accountD := types.Account{
Balance: total,
Frozen: 0,
Addr: string(Nodes[3]),
}
execAddr := address.ExecAddress(et.ExchangeX)
stateDB, _ := dbm.NewGoMemDB("1", "2", 1000)
_, _, kvdb := util.CreateTestDB()
accA, _ := account.NewAccountDB(cfg, "coins", "bty", stateDB)
accA.SaveExecAccount(execAddr, &accountA)
accB, _ := account.NewAccountDB(cfg, "coins", "bty", stateDB)
accB.SaveExecAccount(execAddr, &accountB)
accC, _ := account.NewAccountDB(cfg, "coins", "bty", stateDB)
accC.SaveExecAccount(execAddr, &accountC)
accD, _ := account.NewAccountDB(cfg, "coins", "bty", stateDB)
accD.SaveExecAccount(execAddr, &accountD)
accA1, _ := account.NewAccountDB(cfg, "token", "CCNY", stateDB)
accA1.SaveExecAccount(execAddr, &accountA)
accB1, _ := account.NewAccountDB(cfg, "paracross", "coins.bty", stateDB)
accB1.SaveExecAccount(execAddr, &accountB)
accC1, _ := account.NewAccountDB(cfg, "paracross", "token.CCNY", stateDB)
accC1.SaveExecAccount(execAddr, &accountC)
accD1, _ := account.NewAccountDB(cfg, "token", "para", stateDB)
accD1.SaveExecAccount(execAddr, &accountD)
env := execEnv{
10,
cfg.GetDappFork(et.ExchangeX, "Enable"),
1539918074,
}
// orderlimit bty:CCNY 买bty
ety := types.LoadExecutorType(et.ExchangeX)
tx, err := ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Price: 4, Amount: 10 * types.Coin, Op: et.OpBuy})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyA)
assert.Nil(t, err)
exec := newExchange()
e := exec.(*exchange)
err = e.CheckTx(tx, 1)
assert.Nil(t, err)
q := queue.New("channel")
q.SetConfig(cfg)
api, _ := client.New(q.Client(), nil)
exec.SetAPI(api)
exec.SetStateDB(stateDB)
exec.SetLocalDB(kvdb)
env.blockHeight = env.blockHeight + 1
env.blockTime = env.blockTime + 20
env.difficulty = env.difficulty + 1
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err := exec.Exec(tx, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptData := &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err := exec.ExecLocal(tx, receiptData, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range set.KV {
kvdb.Set(kv.Key, kv.Value)
}
orderID1 := common.ToHex(tx.Hash())
//根据订单号,查询订单详情
msg, err := exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID1}))
if err != nil {
t.Error(err)
}
reply := msg.(*et.Order)
t.Log(reply)
assert.Equal(t, int32(et.Ordered), reply.Status)
assert.Equal(t, 10*types.Coin, reply.GetBalance())
//查看账户余额
acc := accA1.LoadExecAccount(string(Nodes[0]), execAddr)
t.Log(acc)
//根据op查询市场深度
msg, err = exec.Query(et.FuncNameQueryMarketDepth, types.Encode(&et.QueryMarketDepth{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Op: et.OpBuy}))
if err != nil {
t.Error(err)
}
t.Log(msg)
reply1 := msg.(*et.MarketDepthList)
assert.Equal(t, 10*types.Coin, reply1.List[0].GetAmount())
//根据状态和地址查询
msg, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Ordered, Address: string(Nodes[0])}))
if err != nil {
t.Error(err)
}
t.Log(msg)
reply2 := msg.(*et.OrderList)
assert.Equal(t, orderID1, reply2.List[0].OrderID)
// orderlimit bty:CCNY 卖bty
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})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyB)
assert.Nil(t, err)
err = e.CheckTx(tx, 1)
assert.Nil(t, err)
env.blockHeight = env.blockHeight + 1
env.blockTime = env.blockTime + 20
env.difficulty = env.difficulty + 1
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err = exec.Exec(tx, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptData = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(tx, receiptData, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range set.KV {
kvdb.Set(kv.Key, kv.Value)
}
orderID2 := common.ToHex(tx.Hash())
//根据订单号,查询订单详情
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID1}))
if err != nil {
t.Error(err)
}
//订单1的状态应该还是ordered
reply = msg.(*et.Order)
assert.Equal(t, int32(et.Ordered), reply.Status)
t.Log(reply)
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID2}))
if err != nil {
t.Error(err)
}
reply = msg.(*et.Order)
assert.Equal(t, int32(et.Completed), reply.Status)
//根据op查询市场深度
msg, err = exec.Query(et.FuncNameQueryMarketDepth, types.Encode(&et.QueryMarketDepth{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Op: et.OpBuy}))
if err != nil {
t.Error(err)
}
t.Log(msg)
reply1 = msg.(*et.MarketDepthList)
t.Log(reply1.List)
//市场深度应该改变
assert.Equal(t, 5*types.Coin, reply1.List[0].GetAmount())
//QueryCompletedOrderList
msg, err = exec.Query(et.FuncNameQueryCompletedOrderList, types.Encode(&et.QueryCompletedOrderList{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}}))
if err != nil {
t.Error(err)
}
reply2 = msg.(*et.OrderList)
assert.Equal(t, orderID2, reply2.List[0].OrderID)
//撤回之前的订单
// orderlimit bty:CCNY
tx, err = ety.Create("RevokeOrder", &et.RevokeOrder{OrderID: orderID1})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyA)
assert.Nil(t, err)
err = e.CheckTx(tx, 1)
assert.Nil(t, err)
env.blockHeight = env.blockHeight + 1
env.blockTime = env.blockTime + 20
env.difficulty = env.difficulty + 1
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err = exec.Exec(tx, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptData = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(tx, receiptData, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range set.KV {
kvdb.Set(kv.Key, kv.Value)
}
//根据订单号,查询订单详情
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID1}))
if err != nil {
t.Error(err)
}
reply = msg.(*et.Order)
assert.Equal(t, int32(et.Revoked), reply.Status)
t.Log(reply)
//根据op查询市场深度
msg, err = exec.Query(et.FuncNameQueryMarketDepth, types.Encode(&et.QueryMarketDepth{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Op: et.OpBuy}))
if err != nil {
t.Error(err)
}
reply1 = msg.(*et.MarketDepthList)
t.Log(reply1.GetList())
t.Log(len(reply1.GetList()))
//反向测试
// 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})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyA)
assert.Nil(t, err)
err = e.CheckTx(tx, 1)
assert.Nil(t, err)
env.blockHeight = env.blockHeight + 1
env.blockTime = env.blockTime + 20
env.difficulty = env.difficulty + 1
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err = exec.Exec(tx, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptData = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(tx, receiptData, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range set.KV {
kvdb.Set(kv.Key, kv.Value)
}
orderID3 := common.ToHex(tx.Hash())
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID3}))
if err != nil {
t.Error(err)
}
//订单1的状态应该还是ordered
reply = msg.(*et.Order)
assert.Equal(t, int32(et.Ordered), reply.Status)
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})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyA)
assert.Nil(t, err)
err = e.CheckTx(tx, 1)
assert.Nil(t, err)
env.blockHeight = env.blockHeight + 1
env.blockTime = env.blockTime + 20
env.difficulty = env.difficulty + 1
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err = exec.Exec(tx, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptData = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(tx, receiptData, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range set.KV {
kvdb.Set(kv.Key, kv.Value)
}
orderID4 := common.ToHex(tx.Hash())
//根据订单号,查询订单详情
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID4}))
if err != nil {
t.Error(err)
}
//订单1的状态应该还是ordered
reply = msg.(*et.Order)
assert.Equal(t, int32(et.Ordered), reply.Status)
t.Log(reply)
//根据op查询市场深度
msg, err = exec.Query(et.FuncNameQueryMarketDepth, types.Encode(&et.QueryMarketDepth{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Op: et.OpSell}))
if err != nil {
t.Error(err)
}
reply1 = msg.(*et.MarketDepthList)
t.Log(reply1.List)
//市场深度应该改变
assert.Equal(t, 20*types.Coin, reply1.List[0].GetAmount())
//根据状态和地址查询
msg, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Ordered, Address: string(Nodes[0])}))
if err != nil {
t.Error(err)
}
reply2 = msg.(*et.OrderList)
t.Log(reply2)
//默认倒序查询
assert.Equal(t, orderID3, reply2.List[1].OrderID)
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})
assert.Nil(t, err)
tx, err = types.FormatTx(cfg, et.ExchangeX, tx)
assert.Nil(t, err)
tx, err = signTx(tx, PrivKeyB)
assert.Nil(t, err)
err = e.CheckTx(tx, 1)
assert.Nil(t, err)
env.blockHeight = env.blockHeight + 1
env.blockTime = env.blockTime + 20
env.difficulty = env.difficulty + 1
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err = exec.Exec(tx, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptData = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(tx, receiptData, int(1))
if err != nil {
t.Error(err)
}
for _, kv := range set.KV {
kvdb.Set(kv.Key, kv.Value)
}
orderID5 := common.ToHex(tx.Hash())
//根据订单号,查询订单详情
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID5}))
if err != nil {
t.Error(err)
}
//订单1的状态应该还是ordered
reply = msg.(*et.Order)
assert.Equal(t, int32(et.Completed), reply.Status)
//根据op查询市场深度
msg, err = exec.Query(et.FuncNameQueryMarketDepth, types.Encode(&et.QueryMarketDepth{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"},
RightAsset: &et.Asset{Execer: "paracross", Symbol: "coins.bty"}, Op: et.OpSell}))
if err != nil {
t.Error(err)
}
reply1 = msg.(*et.MarketDepthList)
t.Log(reply1.List)
}
func signTx(tx *types.Transaction, hexPrivKey string) (*types.Transaction, error) {
signType := types.SECP256K1
c, err := crypto.New(types.GetSignName("", signType))
if err != nil {
return tx, err
}
bytes, err := common.FromHex(hexPrivKey[:])
if err != nil {
return tx, err
}
privKey, err := c.PrivKeyFromBytes(bytes)
if err != nil {
return tx, err
}
tx.Sign(int32(signType), privKey)
return tx, nil
}
func TestTruncate(t *testing.T) {
a:=float32(1.00000212000000000001)
b:=float32(0.34567)
c:=float32(1234)
a := float32(1.00000212000000000001)
b := float32(0.34567)
c := float32(1234)
t.Log(Truncate(a))
t.Log(Truncate(b))
t.Log(Truncate(c))
}
func TestCheckPrice(t *testing.T) {
t.Log(CheckPrice(0.25))
}
......@@ -35,7 +35,8 @@ func NewAction(e *exchange, tx *types.Transaction, index int) *Action {
//GetIndex get index
func (a *Action) GetIndex() int64 {
return a.height*types.MaxTxsPerBlock + int64(a.index)
//扩容4个0,用于匹配多个matchorder索引时使用
return (a.height*types.MaxTxsPerBlock + int64(a.index)) * 1e4
}
//GetKVSet get kv set
......@@ -66,9 +67,9 @@ func (a *Action) calcActualCost(op int32, amount int64, price float32) int64 {
return amount
}
//price 精度允许范围小数点后面7位数,整数是1e8
//price 精度允许范围小数点后面7位数,0<price<1e8
func CheckPrice(price float32) bool {
if (Truncate(price) > 1e8) || (Truncate(price)*float32(1e8) <= 0) {
if (Truncate(price) >= 1e8) || (Truncate(price)*float32(1e8) <= 0) {
return false
}
return true
......@@ -122,7 +123,7 @@ func (a *Action) LimitOrder(payload *et.LimitOrder) (*types.Receipt, error) {
if !types.CheckAmount(payload.GetAmount()) {
return nil, et.ErrAssetAmount
}
if CheckPrice(payload.GetPrice()) {
if !CheckPrice(payload.GetPrice()) {
return nil, et.ErrAssetPrice
}
if !CheckOp(payload.GetOp()) {
......@@ -188,7 +189,7 @@ func (a *Action) RevokeOrder(payload *et.RevokeOrder) (*types.Receipt, error) {
if err != nil {
return nil, err
}
amount := int64(float32(balance) * price)
amount := a.calcActualCost(et.OpBuy, balance, price)
rightAccount := rightAssetDB.LoadExecAccount(a.fromaddr, a.execaddr)
if rightAccount.Frozen < amount {
elog.Error("RevokeOrder.BalanceCheck", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", amount, "err", et.ErrAssetBalance.Error())
......@@ -207,7 +208,7 @@ func (a *Action) RevokeOrder(payload *et.RevokeOrder) (*types.Receipt, error) {
if err != nil {
return nil, err
}
amount := balance
amount := a.calcActualCost(et.OpBuy, balance, price)
leftAccount := leftAssetDB.LoadExecAccount(a.fromaddr, a.execaddr)
if leftAccount.Frozen < amount {
elog.Error("RevokeOrder.BalanceCheck", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", amount, "err", et.ErrAssetBalance.Error())
......@@ -281,10 +282,29 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
}
//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.GetPrice()))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.GetPrice()), "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.GetPrice()))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, or.GetBalance(), payload.GetPrice()), "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(), payload.GetPrice()))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.GetPrice()), "err", err.Error())
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, or.GetBalance(), payload.GetPrice()), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
......@@ -297,9 +317,9 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
// match receiptorder
matchorder.Balance = matchorder.GetBalance() - or.GetBalance()
matchorder.Executed = matchorder.Executed + or.GetBalance()
}
// match receiptorder,涉及赋值先手顺序,代码顺序不可变
matchorder.Status = func(a, b int64) int32 {
if a > b {
return et.Ordered
......@@ -307,12 +327,15 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
return et.Completed
}
}(matchorder.GetBalance(), or.GetBalance())
matchorder.Balance = matchorder.GetBalance() - or.GetBalance()
matchorder.Executed = matchorder.Executed + or.GetBalance()
a.updateStateDBCache(matchorder)
kvs = append(kvs, a.GetKVSet(matchorder)...)
or.Balance = 0
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)
......@@ -325,10 +348,29 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
receipts := &types.Receipt{Ty: types.ExecOk, KV: kvs, Logs: logs}
return receipts, nil
} else {
if payload.Op == et.OpSell {
//转移冻结资产
receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.GetPrice()))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.GetPrice()), "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.GetPrice()))
if err != nil {
elog.Error("matchLimitOrder.ExecTransfer", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(payload.Op, matchorder.GetBalance(), payload.GetPrice()), "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(), payload.GetPrice()))
if err != nil {
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", a.fromaddr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.GetPrice()), "err", err.Error())
elog.Error("matchLimitOrder.ExecTransferFrozen", "addr", matchorder.Addr, "execaddr", a.execaddr, "amount", a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.GetPrice()), "err", err.Error())
return nil, err
}
logs = append(logs, receipt.Logs...)
......@@ -341,23 +383,23 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
}
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.Balance = 0
matchorder.Executed = matchorder.Executed + matchorder.GetBalance()
matchorder.Status = et.Completed
matchorder.Balance = 0
a.updateStateDBCache(matchorder)
kvs = append(kvs, a.GetKVSet(matchorder)...)
or.Balance = or.Balance - matchorder.Balance
or.Executed = or.Executed + matchorder.Balance
or.Status = et.Ordered
re.Order = or
re.MatchOrders = append(re.MatchOrders, matchorder)
a.updateStateDBCache(or)
}
}
//查询数据不满足5条说明没有了,跳出循环
......
package executor
import (
"fmt"
"github.com/33cn/chain33/types"
exchangetypes "github.com/33cn/plugin/plugin/dapp/exchange/types"
)
......@@ -11,19 +12,16 @@ import (
*/
func (e *exchange) Exec_LimitOrder(payload *exchangetypes.LimitOrder, tx *types.Transaction, index int) (*types.Receipt, error) {
var receipt *types.Receipt
//implement code
return receipt, nil
action := NewAction(e, tx, index)
return action.LimitOrder(payload)
}
func (e *exchange) Exec_MarketOrder(payload *exchangetypes.MarketOrder, tx *types.Transaction, index int) (*types.Receipt, error) {
var receipt *types.Receipt
//implement code
return receipt, nil
//TODO marketOrder
return nil, fmt.Errorf("%s", "not support MarketOrder..")
}
func (e *exchange) Exec_RevokeOrder(payload *exchangetypes.RevokeOrder, tx *types.Transaction, index int) (*types.Receipt, error) {
var receipt *types.Receipt
//implement code
return receipt, nil
action := NewAction(e, tx, index)
return action.RevokeOrder(payload)
}
......@@ -30,13 +30,37 @@ func (e *exchange) ExecLocal_LimitOrder(payload *exchangetypes.LimitOrder, tx *t
func (e *exchange) ExecLocal_MarketOrder(payload *exchangetypes.MarketOrder, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
//implement code
if receiptData.Ty == types.ExecOk {
for _, log := range receiptData.Logs {
switch log.Ty {
case exchangetypes.TyMarketOrderLog:
receipt := &exchangetypes.ReceiptExchange{}
if err := types.Decode(log.Log, receipt); err != nil {
return nil, err
}
kv := e.updateIndex(receipt)
dbSet.KV = append(dbSet.KV, kv...)
}
}
}
return e.addAutoRollBack(tx, dbSet.KV), nil
}
func (e *exchange) ExecLocal_RevokeOrder(payload *exchangetypes.RevokeOrder, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
dbSet := &types.LocalDBSet{}
//implement code
if receiptData.Ty == types.ExecOk {
for _, log := range receiptData.Logs {
switch log.Ty {
case exchangetypes.TyRevokeOrderLog:
receipt := &exchangetypes.ReceiptExchange{}
if err := types.Decode(log.Log, receipt); err != nil {
return nil, err
}
kv := e.updateIndex(receipt)
dbSet.KV = append(dbSet.KV, kv...)
}
}
}
return e.addAutoRollBack(tx, dbSet.KV), nil
}
......@@ -66,8 +90,10 @@ func (e *exchange) updateIndex(receipt *exchangetypes.ReceiptExchange) (kvs []*t
markDepth.RightAsset = right
markDepth.Op = op
markDepth.Amount = receipt.Order.Balance
}
} else {
markDepth.Amount = markDepth.Amount + receipt.Order.Balance
}
//marketDepth
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, op, price), Value: types.Encode(&markDepth)})
//orderID
......@@ -78,23 +104,23 @@ func (e *exchange) updateIndex(receipt *exchangetypes.ReceiptExchange) (kvs []*t
if len(receipt.MatchOrders) > 0 {
//撮合交易更新
var balance int64
for _, matchOrder := range receipt.MatchOrders {
for i, matchOrder := range receipt.MatchOrders {
if matchOrder.Status == exchangetypes.Completed {
// 删除原有状态orderID
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthOrderKey(left, right, matchOrder.GetLimitOrder().Op, price, matchOrder.Index), Value: nil})
//删除原有状态orderID
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Ordered, matchOrder.Addr, matchOrder.Index), Value: nil})
//更新状态为已完成,索引index,改为当前的index
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Completed, matchOrder.Addr, index), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index})})
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Completed, matchOrder.Addr, index+int64(i+1)), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index + int64(i+1)})})
//calcCompletedOrderKey
kvs = append(kvs, &types.KeyValue{Key: calcCompletedOrderKey(left, right, index), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index})})
kvs = append(kvs, &types.KeyValue{Key: calcCompletedOrderKey(left, right, index+int64(i+1)), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index + int64(i+1)})})
}
if matchOrder.Status == exchangetypes.Ordered {
//只需统一更改市场深度状态,其他不需要处理
balance = balance + matchOrder.Balance
}
}
//更改市场深度
//更改匹配市场深度
var matchDepth exchangetypes.MarketDepth
err = findObject(e.GetLocalDB(), calcMarketDepthKey(left, right, OpSwap(op), price), &matchDepth)
if err == types.ErrNotFound {
......@@ -103,10 +129,15 @@ func (e *exchange) updateIndex(receipt *exchangetypes.ReceiptExchange) (kvs []*t
matchDepth.RightAsset = right
matchDepth.Op = OpSwap(op)
matchDepth.Amount = balance
}
} else {
matchDepth.Amount = matchDepth.Amount - receipt.Order.Executed
}
//marketDepth
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, OpSwap(op), price), Value: types.Encode(&matchDepth)})
if matchDepth.Amount == 0 {
//删除
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, OpSwap(op), price), Value: nil})
}
}
return
case exchangetypes.Completed:
......@@ -121,19 +152,22 @@ func (e *exchange) updateIndex(receipt *exchangetypes.ReceiptExchange) (kvs []*t
//user orderID
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Completed, addr, index), Value: types.Encode(&exchangetypes.OrderID{ID: oderID, Index: index})})
//calcCompletedOrderKey
kvs = append(kvs, &types.KeyValue{Key: calcCompletedOrderKey(left, right, index), Value: types.Encode(&exchangetypes.OrderID{ID: oderID, Index: index})})
if len(receipt.MatchOrders) > 0 {
//撮合交易更新
var balance int64
for _, matchOrder := range receipt.MatchOrders {
for i, matchOrder := range receipt.MatchOrders {
if matchOrder.Status == exchangetypes.Completed {
// 删除原有状态orderID
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthOrderKey(left, right, matchOrder.GetLimitOrder().Op, price, matchOrder.Index), Value: nil})
//删除原有状态orderID
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Ordered, matchOrder.Addr, matchOrder.Index), Value: nil})
//更新状态为已完成,更新索引
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Completed, matchOrder.Addr, index), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index})})
kvs = append(kvs, &types.KeyValue{Key: calcUserOrderIDKey(exchangetypes.Completed, matchOrder.Addr, index+int64(i+1)), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index + int64(i+1)})})
//calcCompletedOrderKey
kvs = append(kvs, &types.KeyValue{Key: calcCompletedOrderKey(left, right, index), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index})})
kvs = append(kvs, &types.KeyValue{Key: calcCompletedOrderKey(left, right, index+int64(i+1)), Value: types.Encode(&exchangetypes.OrderID{ID: matchOrder.OrderID, Index: index + int64(i+1)})})
}
if matchOrder.Status == exchangetypes.Ordered {
......@@ -150,10 +184,15 @@ func (e *exchange) updateIndex(receipt *exchangetypes.ReceiptExchange) (kvs []*t
matchDepth.RightAsset = right
matchDepth.Op = OpSwap(op)
matchDepth.Amount = balance
}
} else {
matchDepth.Amount = matchDepth.Amount - receipt.Order.Executed
}
//marketDepth
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, OpSwap(op), price), Value: types.Encode(&matchDepth)})
if matchDepth.Amount == 0 {
//删除
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, OpSwap(op), price), Value: nil})
}
}
return
case exchangetypes.Revoked:
......@@ -170,8 +209,14 @@ func (e *exchange) updateIndex(receipt *exchangetypes.ReceiptExchange) (kvs []*t
if err == nil {
//marketDepth
marketDepth.Amount = marketDepth.Amount - receipt.GetOrder().Balance
elog.Error("revoked", "recept.order.balance", receipt.GetOrder().Balance)
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, op, price), Value: types.Encode(&marketDepth)})
}
elog.Error("revoked", "marketDepth.Amount", marketDepth.Amount)
if marketDepth.Amount == 0 {
//删除
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthKey(left, right, op, price), Value: nil})
}
// 删除原有状态orderID
kvs = append(kvs, &types.KeyValue{Key: calcMarketDepthOrderKey(left, right, op, price, receipt.GetOrder().Index), Value: nil})
//删除原有状态orderID
......
......@@ -45,14 +45,15 @@ func calcMarketDepthOrderPrefix(left, right *types.Asset, op int32, price float3
// localdb中存储市场挂单ID
func calcMarketDepthOrderKey(left, right *types.Asset, op int32, price float32, index int64) []byte {
// 设置精度为1e8
key := fmt.Sprintf("%s"+"order-%s-%s-%d:%016d:%018d", KeyPrefixLocalDB, left.GetSymbol(), right.GetSymbol(), op, int64(Truncate(price)*float32(1e8)), index)
key := fmt.Sprintf("%s"+"order-%s-%s-%d:%016d:%022d", KeyPrefixLocalDB, left.GetSymbol(), right.GetSymbol(), op, int64(Truncate(price)*float32(1e8)), index)
return []byte(key)
}
//最新已经成交的订单,这里状态固定都是完成状态,这个主要给外部使用,可以查询最新得成交信息
//最新已经成交的订单,这里状态固定都是完成状态,这个主要给外部使用,可以查询最新得成交信息,存在多个情况
//matchOrderIndex,是匹配order在数组中的index,这里预留4个0000,用来解决同一笔交易中存在key重复得情况,这样设计保证了key得唯一性
func calcCompletedOrderKey(left, right *types.Asset, index int64) []byte {
// 设置精度为1e8
key := fmt.Sprintf("%s"+"completed-%s-%s-%d:%018d", KeyPrefixLocalDB, left.GetSymbol(), right.GetSymbol(), types.Completed, index)
key := fmt.Sprintf("%s"+"completed-%s-%s-%d:%022d", KeyPrefixLocalDB, left.GetSymbol(), right.GetSymbol(), types.Completed, index)
return []byte(key)
}
func calcCompletedOrderPrefix(left, right *types.Asset) []byte {
......@@ -66,7 +67,8 @@ func calcUserOrderIDPrefix(status int32, addr string) []byte {
key := fmt.Sprintf("%s"+"addr:%s:%d:", KeyPrefixLocalDB, addr, status)
return []byte(key)
}
//matchOrderIndex,用来解决同一笔交易中存在key重复得情况,这样设计保证了key得唯一性
func calcUserOrderIDKey(status int32, addr string, index int64) []byte {
key := fmt.Sprintf("%s"+"addr:%s:%d:%018d", KeyPrefixLocalDB, addr, status, index)
key := fmt.Sprintf("%s"+"addr:%s:%d:%022d", KeyPrefixLocalDB, addr, status, index)
return []byte(key)
}
......@@ -16,9 +16,6 @@ func (s *exchange) Query_QueryMarketDepth(in *et.QueryMarketDepth) (types.Messag
if !CheckExchangeAsset(in.LeftAsset, in.RightAsset) {
return nil, et.ErrAsset
}
if !CheckPrice(in.Price) {
return nil, et.ErrAssetPrice
}
if !CheckOp(in.Op) {
return nil, et.ErrAssetOp
......
......@@ -7,7 +7,7 @@ message Exchange {
message ExchangeAction {
oneof value {
LimitOrder limitOrder = 1;
// MarketOrder marketOrder = 2;
MarketOrder marketOrder = 2;
RevokeOrder revokeOrder = 3;
}
int32 ty = 6;
......
......@@ -59,6 +59,7 @@ func (*Exchange) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0}
type ExchangeAction struct {
// Types that are valid to be assigned to Value:
// *ExchangeAction_LimitOrder
// *ExchangeAction_MarketOrder
// *ExchangeAction_RevokeOrder
Value isExchangeAction_Value `protobuf_oneof:"value"`
Ty int32 `protobuf:"varint,6,opt,name=ty" json:"ty,omitempty"`
......@@ -76,11 +77,15 @@ type isExchangeAction_Value interface {
type ExchangeAction_LimitOrder struct {
LimitOrder *LimitOrder `protobuf:"bytes,1,opt,name=limitOrder,oneof"`
}
type ExchangeAction_MarketOrder struct {
MarketOrder *MarketOrder `protobuf:"bytes,2,opt,name=marketOrder,oneof"`
}
type ExchangeAction_RevokeOrder struct {
RevokeOrder *RevokeOrder `protobuf:"bytes,3,opt,name=revokeOrder,oneof"`
}
func (*ExchangeAction_LimitOrder) isExchangeAction_Value() {}
func (*ExchangeAction_MarketOrder) isExchangeAction_Value() {}
func (*ExchangeAction_RevokeOrder) isExchangeAction_Value() {}
func (m *ExchangeAction) GetValue() isExchangeAction_Value {
......@@ -97,6 +102,13 @@ func (m *ExchangeAction) GetLimitOrder() *LimitOrder {
return nil
}
func (m *ExchangeAction) GetMarketOrder() *MarketOrder {
if x, ok := m.GetValue().(*ExchangeAction_MarketOrder); ok {
return x.MarketOrder
}
return nil
}
func (m *ExchangeAction) GetRevokeOrder() *RevokeOrder {
if x, ok := m.GetValue().(*ExchangeAction_RevokeOrder); ok {
return x.RevokeOrder
......@@ -115,6 +127,7 @@ func (m *ExchangeAction) GetTy() int32 {
func (*ExchangeAction) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _ExchangeAction_OneofMarshaler, _ExchangeAction_OneofUnmarshaler, _ExchangeAction_OneofSizer, []interface{}{
(*ExchangeAction_LimitOrder)(nil),
(*ExchangeAction_MarketOrder)(nil),
(*ExchangeAction_RevokeOrder)(nil),
}
}
......@@ -128,6 +141,11 @@ func _ExchangeAction_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
if err := b.EncodeMessage(x.LimitOrder); err != nil {
return err
}
case *ExchangeAction_MarketOrder:
b.EncodeVarint(2<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.MarketOrder); err != nil {
return err
}
case *ExchangeAction_RevokeOrder:
b.EncodeVarint(3<<3 | proto.WireBytes)
if err := b.EncodeMessage(x.RevokeOrder); err != nil {
......@@ -151,6 +169,14 @@ func _ExchangeAction_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto
err := b.DecodeMessage(msg)
m.Value = &ExchangeAction_LimitOrder{msg}
return true, err
case 2: // value.marketOrder
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
msg := new(MarketOrder)
err := b.DecodeMessage(msg)
m.Value = &ExchangeAction_MarketOrder{msg}
return true, err
case 3: // value.revokeOrder
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
......@@ -173,6 +199,11 @@ func _ExchangeAction_OneofSizer(msg proto.Message) (n int) {
n += proto.SizeVarint(1<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *ExchangeAction_MarketOrder:
s := proto.Size(x.MarketOrder)
n += proto.SizeVarint(2<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(s))
n += s
case *ExchangeAction_RevokeOrder:
s := proto.Size(x.RevokeOrder)
n += proto.SizeVarint(3<<3 | proto.WireBytes)
......@@ -931,46 +962,47 @@ var _Exchange_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("exchange.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 649 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xdd, 0x6a, 0xd4, 0x40,
0x14, 0xee, 0xe4, 0xa7, 0xdb, 0x9c, 0x95, 0xad, 0x0e, 0xa2, 0x41, 0x44, 0x96, 0x5c, 0xd4, 0x22,
0xba, 0x42, 0x0b, 0xfe, 0x5c, 0x56, 0x2b, 0xb4, 0x50, 0xa9, 0x0e, 0xbe, 0x40, 0x9a, 0x1c, 0xbb,
0xa1, 0xd9, 0x4d, 0x48, 0x26, 0xa5, 0x8b, 0xaf, 0x20, 0x78, 0xe7, 0x13, 0x28, 0xf8, 0x12, 0xe2,
0xab, 0xc9, 0x9c, 0x4c, 0x32, 0xd3, 0x1f, 0xa9, 0xa0, 0x7b, 0x97, 0x6f, 0xce, 0x4f, 0xbe, 0x73,
0xce, 0x77, 0x66, 0x60, 0x84, 0x67, 0xc9, 0x34, 0x9e, 0x1f, 0xe3, 0xa4, 0xac, 0x0a, 0x59, 0x70,
0x5f, 0x2e, 0x4a, 0xac, 0x23, 0x80, 0xb5, 0x37, 0xda, 0x10, 0x7d, 0x65, 0x30, 0xea, 0xc0, 0x4e,
0x22, 0xb3, 0x62, 0xce, 0xb7, 0x01, 0xf2, 0x6c, 0x96, 0xc9, 0xc3, 0x2a, 0xc5, 0x2a, 0x64, 0x63,
0xb6, 0x39, 0xdc, 0xba, 0x35, 0xa1, 0xd0, 0xc9, 0x41, 0x6f, 0xd8, 0x5b, 0x11, 0x96, 0x1b, 0x7f,
0x06, 0xc3, 0x0a, 0x4f, 0x8b, 0x13, 0x6c, 0xa3, 0x5c, 0x8a, 0xe2, 0x3a, 0x4a, 0x18, 0xcb, 0xde,
0x8a, 0xb0, 0x1d, 0xf9, 0x08, 0x1c, 0xb9, 0x08, 0x57, 0xc7, 0x6c, 0xd3, 0x17, 0x8e, 0x5c, 0xbc,
0x1a, 0x80, 0x7f, 0x1a, 0xe7, 0x0d, 0x46, 0xdf, 0x18, 0x80, 0xf9, 0x1b, 0x7f, 0x04, 0x41, 0x8e,
0x1f, 0xe5, 0x4e, 0x5d, 0xa3, 0xd4, 0x9c, 0x6e, 0xe8, 0xec, 0x74, 0x26, 0x8c, 0x99, 0x3f, 0x06,
0xa8, 0xb2, 0xe3, 0xa9, 0x76, 0x76, 0xae, 0x70, 0xb6, 0xec, 0xfc, 0x36, 0xf8, 0x65, 0x95, 0x25,
0x48, 0x9c, 0x1d, 0xd1, 0x02, 0x7e, 0x07, 0x56, 0xe3, 0x59, 0xd1, 0xcc, 0x65, 0xe8, 0x8d, 0xd9,
0xa6, 0x2b, 0x34, 0x52, 0x7c, 0x8b, 0x32, 0xf4, 0x5b, 0xbe, 0x45, 0x19, 0x7d, 0x61, 0x30, 0x7c,
0x1b, 0x57, 0x27, 0xb8, 0x74, 0x9e, 0x86, 0x91, 0x7b, 0x05, 0x23, 0xaf, 0x67, 0xf4, 0x10, 0x86,
0x56, 0xbf, 0x79, 0x08, 0x83, 0x42, 0x7d, 0xec, 0xef, 0x12, 0x9d, 0x40, 0x74, 0x30, 0x7a, 0x0e,
0x7e, 0x9f, 0x19, 0xcf, 0x30, 0xd1, 0xc3, 0x0e, 0x84, 0x46, 0xea, 0xbc, 0x5e, 0xcc, 0x8e, 0x8a,
0x9c, 0xb8, 0x05, 0x42, 0xa3, 0xe8, 0x97, 0x03, 0xfe, 0x35, 0xc9, 0x2f, 0x88, 0xc8, 0xf9, 0x6b,
0x11, 0xcd, 0x4c, 0x2f, 0x2f, 0x88, 0xc8, 0xea, 0xb2, 0x12, 0x91, 0xe5, 0xa8, 0x45, 0xe4, 0x75,
0x22, 0xe2, 0xf7, 0x60, 0x4d, 0x95, 0xd0, 0x48, 0x4c, 0x69, 0x54, 0xae, 0xe8, 0xb1, 0xa2, 0x7c,
0x14, 0xe7, 0xf1, 0x3c, 0x41, 0x52, 0x9d, 0x2b, 0x3a, 0x48, 0xe5, 0xca, 0x58, 0x36, 0x75, 0x38,
0xa0, 0x4c, 0x1a, 0x71, 0x0e, 0x5e, 0x9c, 0xa6, 0x55, 0xb8, 0x46, 0x15, 0xd2, 0x37, 0x7f, 0x00,
0xd0, 0x94, 0x69, 0x2c, 0xf1, 0x43, 0x36, 0xc3, 0x30, 0xa0, 0x44, 0xd6, 0x89, 0x12, 0x55, 0x36,
0x4f, 0xf1, 0x2c, 0x04, 0x32, 0xb5, 0xc0, 0x88, 0xfb, 0x05, 0x00, 0x31, 0x7f, 0x47, 0x5a, 0xeb,
0x15, 0xc8, 0x6c, 0x05, 0xf6, 0x29, 0x1c, 0x2b, 0x45, 0xf4, 0x14, 0x06, 0x87, 0xba, 0xc5, 0x23,
0x70, 0xfa, 0xbe, 0x3b, 0xfb, 0xbb, 0x7f, 0x08, 0xf8, 0xc1, 0xe0, 0xe6, 0xfb, 0x06, 0xab, 0x45,
0xdb, 0xbf, 0x5d, 0x2c, 0xe5, 0x74, 0x89, 0x2a, 0x6d, 0xd5, 0xe8, 0x76, 0x6a, 0x34, 0xb5, 0x79,
0x17, 0x6a, 0x4b, 0x48, 0xca, 0xed, 0x22, 0xb5, 0x20, 0xfa, 0xde, 0xef, 0xd2, 0xb2, 0x59, 0xfe,
0xdb, 0xce, 0xbf, 0x84, 0x75, 0x8b, 0xe6, 0x41, 0x56, 0x4b, 0xbe, 0x01, 0x5e, 0x9e, 0xd5, 0x8a,
0xa5, 0x7b, 0x49, 0xb2, 0xe4, 0x25, 0xc8, 0x1e, 0xfd, 0x64, 0x70, 0x97, 0xa6, 0xf1, 0xba, 0x98,
0x95, 0x39, 0x4a, 0x4c, 0x69, 0x9a, 0x94, 0x63, 0xa9, 0xe5, 0xb6, 0xca, 0x70, 0x2d, 0x65, 0x98,
0x21, 0x78, 0xd6, 0x10, 0xf8, 0x7d, 0x08, 0xd2, 0xac, 0x42, 0x7a, 0x0a, 0x74, 0xcd, 0xe6, 0x20,
0xda, 0x00, 0x20, 0xfa, 0xd7, 0xdd, 0x2d, 0x9f, 0x19, 0x8c, 0x8c, 0x23, 0x95, 0x67, 0xd6, 0x8b,
0x9d, 0x5b, 0xaf, 0x10, 0x06, 0x6a, 0xa5, 0xb0, 0xae, 0xf5, 0x35, 0xd3, 0xc1, 0xff, 0x48, 0xfb,
0x09, 0x04, 0x86, 0xc8, 0xf8, 0xdc, 0xac, 0xba, 0xae, 0x91, 0x5d, 0x4f, 0xe9, 0x13, 0xac, 0x0b,
0x4c, 0x30, 0x2b, 0x65, 0xf7, 0x34, 0xf2, 0x08, 0xfc, 0xc2, 0x7a, 0x0f, 0xcf, 0x47, 0xb5, 0x26,
0x3e, 0x51, 0xd7, 0x97, 0x4c, 0xa6, 0x74, 0xa8, 0xaa, 0xb9, 0x9c, 0xdf, 0x76, 0xb8, 0xba, 0xbe,
0x2d, 0x50, 0x97, 0x57, 0xfb, 0xd7, 0xa3, 0x55, 0x7a, 0xb7, 0xb7, 0x7f, 0x07, 0x00, 0x00, 0xff,
0xff, 0xe3, 0xe1, 0xf2, 0x06, 0xc9, 0x07, 0x00, 0x00,
// 658 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xed, 0x6a, 0xd4, 0x4c,
0x14, 0x6e, 0xbe, 0xba, 0xcd, 0xd9, 0x97, 0xed, 0xeb, 0x20, 0x1a, 0x44, 0x64, 0xc9, 0x8f, 0x5a,
0x44, 0x57, 0x68, 0xc1, 0x8f, 0x9f, 0xd5, 0x0a, 0x2d, 0x54, 0xaa, 0x83, 0x37, 0x90, 0x26, 0xc7,
0x6e, 0x68, 0x76, 0x13, 0x92, 0xd9, 0xd2, 0xc5, 0x5b, 0x10, 0xbc, 0x09, 0x05, 0x6f, 0x42, 0xbc,
0x03, 0xaf, 0x49, 0xe6, 0xcc, 0x24, 0x33, 0xdb, 0xae, 0x54, 0xd4, 0xfd, 0x97, 0x67, 0xce, 0x47,
0x9e, 0x73, 0xce, 0x73, 0x66, 0x60, 0x80, 0x17, 0xe9, 0x38, 0x99, 0x9e, 0xe2, 0xa8, 0xaa, 0x4b,
0x51, 0xb2, 0x40, 0xcc, 0x2b, 0x6c, 0x62, 0x80, 0x8d, 0x57, 0xda, 0x10, 0xff, 0x70, 0x60, 0xd0,
0x82, 0xbd, 0x54, 0xe4, 0xe5, 0x94, 0xed, 0x02, 0x14, 0xf9, 0x24, 0x17, 0xc7, 0x75, 0x86, 0x75,
0xe4, 0x0c, 0x9d, 0xed, 0xfe, 0xce, 0x8d, 0x11, 0x85, 0x8e, 0x8e, 0x3a, 0xc3, 0xc1, 0x1a, 0xb7,
0xdc, 0xd8, 0x13, 0xe8, 0x4f, 0x92, 0xfa, 0x0c, 0x75, 0x94, 0x4b, 0x51, 0x4c, 0x47, 0xbd, 0x36,
0x96, 0x83, 0x35, 0x6e, 0x3b, 0xca, 0xb8, 0x1a, 0xcf, 0xcb, 0x33, 0x54, 0x71, 0xde, 0x42, 0x1c,
0x37, 0x16, 0x19, 0x67, 0x39, 0xb2, 0x01, 0xb8, 0x62, 0x1e, 0xad, 0x0f, 0x9d, 0xed, 0x80, 0xbb,
0x62, 0xfe, 0xa2, 0x07, 0xc1, 0x79, 0x52, 0xcc, 0x30, 0xfe, 0xec, 0x00, 0x18, 0x96, 0xec, 0x01,
0x84, 0x05, 0xbe, 0x17, 0x7b, 0x4d, 0x83, 0x42, 0xd7, 0xf2, 0x9f, 0xce, 0x4e, 0x67, 0xdc, 0x98,
0xd9, 0x43, 0x80, 0x3a, 0x3f, 0x1d, 0x6b, 0x67, 0x77, 0x89, 0xb3, 0x65, 0x67, 0x37, 0x21, 0xa8,
0xea, 0x3c, 0x45, 0xe2, 0xec, 0x72, 0x05, 0xd8, 0x2d, 0x58, 0x4f, 0x26, 0xe5, 0x6c, 0x2a, 0x22,
0x7f, 0xe8, 0x6c, 0x7b, 0x5c, 0x23, 0xc9, 0xb7, 0xac, 0xa2, 0x40, 0xf1, 0x2d, 0xab, 0xf8, 0x93,
0x03, 0x7d, 0xab, 0x2d, 0x2b, 0xe4, 0x69, 0x18, 0x79, 0x4b, 0x18, 0xf9, 0x1d, 0xa3, 0xfb, 0xd0,
0xb7, 0xfa, 0xcd, 0x22, 0xe8, 0x95, 0xf2, 0xe3, 0x70, 0x9f, 0xe8, 0x84, 0xbc, 0x85, 0xf1, 0x53,
0x08, 0xba, 0xcc, 0x78, 0x81, 0xa9, 0x16, 0x49, 0xc8, 0x35, 0x92, 0xe7, 0xcd, 0x7c, 0x72, 0x52,
0x16, 0xc4, 0x2d, 0xe4, 0x1a, 0xc5, 0xdf, 0x5d, 0x08, 0xae, 0x49, 0x7e, 0x49, 0x7c, 0xee, 0x1f,
0x89, 0xcf, 0xfb, 0x5d, 0xf1, 0x29, 0x11, 0xf9, 0xad, 0x88, 0xd8, 0x1d, 0xd8, 0x90, 0x25, 0xcc,
0x04, 0x66, 0x34, 0x2a, 0x8f, 0x77, 0x58, 0x52, 0x3e, 0x49, 0x8a, 0x64, 0x9a, 0x22, 0xa9, 0xce,
0xe3, 0x2d, 0xa4, 0x72, 0x45, 0x22, 0x66, 0x4d, 0xd4, 0xa3, 0x4c, 0x1a, 0x31, 0x06, 0x7e, 0x92,
0x65, 0x75, 0xb4, 0x41, 0x15, 0xd2, 0x37, 0xbb, 0x07, 0x30, 0xab, 0xb2, 0x44, 0xe0, 0xbb, 0x7c,
0x82, 0x51, 0x48, 0x89, 0xac, 0x13, 0x29, 0xaa, 0x7c, 0x9a, 0xe1, 0x45, 0x04, 0x64, 0x52, 0xc0,
0x88, 0xfb, 0x19, 0x00, 0x31, 0x7f, 0x43, 0x5a, 0xeb, 0x14, 0xe8, 0xd8, 0x0a, 0xec, 0x52, 0xb8,
0x56, 0x8a, 0xf8, 0x31, 0xf4, 0x8e, 0x75, 0x8b, 0x07, 0xe0, 0x76, 0x7d, 0x77, 0x0f, 0xf7, 0x7f,
0x11, 0xf0, 0xd5, 0x81, 0xff, 0xdf, 0xce, 0xb0, 0x9e, 0xab, 0xfe, 0xed, 0x63, 0x25, 0xc6, 0x2b,
0x54, 0xa9, 0x52, 0xa3, 0xd7, 0xaa, 0xd1, 0xd4, 0xe6, 0x5f, 0xaa, 0x2d, 0x25, 0x29, 0xab, 0x45,
0x52, 0x20, 0xfe, 0xd2, 0xed, 0xd2, 0xaa, 0x59, 0xfe, 0xdd, 0xce, 0x3f, 0x87, 0x4d, 0x8b, 0xe6,
0x51, 0xde, 0x08, 0xb6, 0x05, 0x7e, 0x91, 0x37, 0x92, 0xa5, 0x77, 0x45, 0xb2, 0xe4, 0xc5, 0xc9,
0x1e, 0x7f, 0x73, 0xe0, 0x36, 0x4d, 0xe3, 0x65, 0x39, 0xa9, 0x0a, 0x14, 0x98, 0xd1, 0x34, 0x29,
0xc7, 0x4a, 0xcb, 0x55, 0xca, 0xf0, 0x2c, 0x65, 0x98, 0x21, 0xf8, 0xd6, 0x10, 0xd8, 0x5d, 0x08,
0xb3, 0xbc, 0x46, 0x7a, 0x42, 0x74, 0xcd, 0xe6, 0x20, 0xde, 0x02, 0x20, 0xfa, 0xd7, 0xdd, 0x2d,
0x1f, 0x1d, 0x18, 0x18, 0x47, 0x2a, 0xcf, 0xac, 0x97, 0xb3, 0xb0, 0x5e, 0x11, 0xf4, 0xe4, 0x4a,
0x61, 0xd3, 0xe8, 0x6b, 0xa6, 0x85, 0xff, 0x90, 0xf6, 0x23, 0x08, 0x0d, 0x91, 0xe1, 0xc2, 0xac,
0xda, 0xae, 0x91, 0x5d, 0x4f, 0xe9, 0x03, 0x6c, 0x72, 0x4c, 0x31, 0xaf, 0x44, 0xfb, 0xa4, 0xb2,
0x18, 0x82, 0xd2, 0x7a, 0x47, 0x17, 0xa3, 0x94, 0x89, 0x8d, 0xe4, 0xf5, 0x25, 0xd2, 0x31, 0x1d,
0xca, 0x6a, 0xae, 0xe6, 0xb7, 0x1d, 0x96, 0xd7, 0xb7, 0x03, 0xf2, 0xf2, 0x52, 0x7f, 0x3d, 0x59,
0xa7, 0xf7, 0x7e, 0xf7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x7b, 0x52, 0x3b, 0x01, 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