Commit 2dfb0996 authored by harrylee's avatar harrylee Committed by vipwzw

add readme

parent f941df38
# exchange合约
## 前言
这是一个基于chain33开发的去中心化交易所合约,不收任何手续费,用于满足一小部分人群或者其他特定业务场景中,虚拟资产之间得交换。
## 使用
合约提供了类似中心化交易所健全的查询接口,所有得接口设计都基于用户的角度去出发
合约接口,在线构造交易和查询接口分别复用了框架中的CreateTransaction和Query接口,详情请参考
[CreateTransaction接口](https://github.com/33cn/chain33/blob/master/rpc/jrpchandler.go#L1101)[Query接口](https://github.com/33cn/chain33/blob/master/rpc/jrpchandler.go#L838)
查询方法名称|功能
-----|----
QueryMarketDepth|获取指定交易资产的市场深度
QueryCompletedOrderList|实时获取指定交易对最新的成交信息
QueryOrder|根据orderID订单号查询具体的订单信息
QueryOrderList|根据用户地址和订单状态(ordered,completed,revoked),实时地获取相应相应的订单详情
可参照exchange_test.go中得相关测试用例,构建limitOrder或者revokeOrder交易进行相关测试
## 注意事项
合约撮合规则如下:
序号|规则
---|----
1|买家获利得原则
2|买单高于市场价,按价格由低往高撮合
3|卖单低于市场价,按价格由高往低进行撮合
4|价格相同按先进先出的原则进行撮合
5|出于系统安全考虑,最大撮合深度为100单,单笔挂单最小为1e8,就是一个bty
...@@ -289,14 +289,30 @@ func TestExchange(t *testing.T) { ...@@ -289,14 +289,30 @@ func TestExchange(t *testing.T) {
assert.Equal(t, int32(et.Revoked), reply.Status) assert.Equal(t, int32(et.Revoked), reply.Status)
t.Log(reply) t.Log(reply)
//根据op查询市场深度 //根据op查询市场深度
//查看bty,CCNY买市场深度,查不到买单深度
msg, err = exec.Query(et.FuncNameQueryMarketDepth, types.Encode(&et.QueryMarketDepth{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"}, 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})) RightAsset: &et.Asset{Execer: "token", Symbol: "CCNY"}, Op: et.OpBuy}))
assert.Equal(t, types.ErrNotFound, err)
//根据原有状态去查看买单是否被改变
//ordered状态应该查不到数据
_, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Ordered, Address: string(Nodes[0])}))
assert.Equal(t, types.ErrNotFound, err)
msg, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Revoked, Address: string(Nodes[0])}))
//reovked状态肯定有数据
if err != nil {
t.Log(err)
}
t.Log(msg)
assert.Equal(t, orderID1, msg.(*et.OrderList).List[0].OrderID)
//根据订单号,查询订单详情
msg, err = exec.Query(et.FuncNameQueryOrder, types.Encode(&et.QueryOrder{OrderID: orderID1}))
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
reply1 = msg.(*et.MarketDepthList) t.Log(msg.(*et.Order))
t.Log(reply1.GetList()) assert.Equal(t, int32(et.Revoked), msg.(*et.Order).Status)
t.Log(len(reply1.GetList())) assert.Equal(t, 5*types.Coin, msg.(*et.Order).GetBalance())
//反向测试 //反向测试
// orderlimit bty:CCNY 卖bty // orderlimit bty:CCNY 卖bty
...@@ -530,7 +546,13 @@ func TestExchange(t *testing.T) { ...@@ -530,7 +546,13 @@ func TestExchange(t *testing.T) {
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
//查看bty,CCNY买市场深度
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}))
t.Log(msg) t.Log(msg)
if err != nil {
assert.Equal(t, types.ErrNotFound, err)
}
tx, err = ety.Create("LimitOrder", &et.LimitOrder{LeftAsset: &et.Asset{Symbol: "bty", Execer: "coins"}, 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: 3, Amount: 10 * types.Coin, Op: et.OpSell})
...@@ -564,6 +586,27 @@ func TestExchange(t *testing.T) { ...@@ -564,6 +586,27 @@ func TestExchange(t *testing.T) {
//save to database //save to database
util.SaveKVList(stateDB, set.KV) util.SaveKVList(stateDB, set.KV)
//查看订单6的状态是否正确
msg, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Completed, Address: string(Nodes[3])}))
if err != nil {
t.Error(err)
}
orderList = msg.(*et.OrderList)
t.Log(orderList.List[0])
assert.Equal(t, orderID6, orderList.List[0].OrderID)
//检查原有ordered状态得订单数据是否正确被删除
_, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Ordered, Address: string(Nodes[3])}))
assert.Equal(t, types.ErrNotFound, err)
//查看bty,CCNY买市场深度
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}))
t.Log(msg)
if err != nil {
assert.Equal(t, types.ErrNotFound, err)
}
//根据地址状态查看订单 //根据地址状态查看订单
msg, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Ordered, Address: string(Nodes[2])})) msg, err = exec.Query(et.FuncNameQueryOrderList, types.Encode(&et.QueryOrderList{Status: et.Ordered, Address: string(Nodes[2])}))
if err != nil { if err != nil {
......
...@@ -6,10 +6,8 @@ import ( ...@@ -6,10 +6,8 @@ import (
"github.com/33cn/chain33/account" "github.com/33cn/chain33/account"
"github.com/33cn/chain33/client" "github.com/33cn/chain33/client"
//"github.com/33cn/chain33/common"
dbm "github.com/33cn/chain33/common/db" dbm "github.com/33cn/chain33/common/db"
. "github.com/33cn/chain33/common/db/table" tab "github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/system/dapp" "github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types" "github.com/33cn/chain33/types"
et "github.com/33cn/plugin/plugin/dapp/exchange/types" et "github.com/33cn/plugin/plugin/dapp/exchange/types"
...@@ -172,7 +170,6 @@ func (a *Action) RevokeOrder(payload *et.RevokeOrder) (*types.Receipt, error) { ...@@ -172,7 +170,6 @@ func (a *Action) RevokeOrder(payload *et.RevokeOrder) (*types.Receipt, error) {
var logs []*types.ReceiptLog var logs []*types.ReceiptLog
var kvs []*types.KeyValue var kvs []*types.KeyValue
order, err := findOrderByOrderID(a.statedb, a.localDB, payload.GetOrderID()) order, err := findOrderByOrderID(a.statedb, a.localDB, payload.GetOrderID())
elog.Info("RevokeOrder====", "order", order.Balance)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -388,7 +385,6 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc ...@@ -388,7 +385,6 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
return receipts, nil return receipts, nil
} }
if payload.Op == et.OpSell { if payload.Op == et.OpSell {
elog.Info("matchLimitOrder.findOrderByOrderID========", "order", matchorder)
//转移冻结资产 //转移冻结资产
receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.Price)) receipt, err := rightAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), payload.Price))
if err != nil { if err != nil {
...@@ -419,7 +415,6 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc ...@@ -419,7 +415,6 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
kvs = append(kvs, receipt.KV...) kvs = append(kvs, receipt.KV...)
} }
if payload.Op == et.OpBuy { if payload.Op == et.OpBuy {
elog.Info("matchLimitOrder.findOrderByOrderID++++++", "order", matchorder)
//转移冻结资产 //转移冻结资产
receipt, err := leftAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price)) receipt, err := leftAccountDB.ExecTransferFrozen(matchorder.Addr, a.fromaddr, a.execaddr, a.calcActualCost(matchorder.GetLimitOrder().Op, matchorder.GetBalance(), matchorder.GetLimitOrder().Price))
if err != nil { if err != nil {
...@@ -501,7 +496,7 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc ...@@ -501,7 +496,7 @@ func (a *Action) matchLimitOrder(payload *et.LimitOrder, leftAccountDB, rightAcc
//根据订单号查询,分为两步,优先去localdb中查询,如没有则再去状态数据库中查询 //根据订单号查询,分为两步,优先去localdb中查询,如没有则再去状态数据库中查询
// 1.挂单中得订单信会根据orderID在localdb中存储 // 1.挂单中得订单信会根据orderID在localdb中存储
// 2.订单撤销,或者成交后,根据orderID在localdb中存储得数据会被删除,这时只能到状态数据库中查询 // 2.订单撤销,或者成交后,根据orderID在localdb中存储得数据会被删除,这时只能到状态数据库中查询
func findOrderByOrderID(statedb dbm.KV, localdb dbm.KVDB, orderID int64) (*et.Order, error) { func findOrderByOrderID(statedb dbm.KV, localdb dbm.KV, orderID int64) (*et.Order, error) {
table := NewMarketOrderTable(localdb) table := NewMarketOrderTable(localdb)
primaryKey := []byte(fmt.Sprintf("%022d", orderID)) primaryKey := []byte(fmt.Sprintf("%022d", orderID))
row, err := table.GetData(primaryKey) row, err := table.GetData(primaryKey)
...@@ -523,17 +518,16 @@ func findOrderByOrderID(statedb dbm.KV, localdb dbm.KVDB, orderID int64) (*et.Or ...@@ -523,17 +518,16 @@ func findOrderByOrderID(statedb dbm.KV, localdb dbm.KVDB, orderID int64) (*et.Or
} }
func findOrderIDListByPrice(localdb dbm.KVDB, left, right *et.Asset, price float64, op, direction int32, primaryKey string) (*et.OrderList, error) { func findOrderIDListByPrice(localdb dbm.KV, left, right *et.Asset, price float64, op, direction int32, primaryKey string) (*et.OrderList, error) {
table := NewMarketOrderTable(localdb) table := NewMarketOrderTable(localdb)
query := table.GetQuery(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, int64(Truncate(price*float64(1e8)))))
var rows []*Row var rows []*tab.Row
var err error var err error
if primaryKey == "" { //第一次查询,默认展示最新得成交记录 if primaryKey == "" { //第一次查询,默认展示最新得成交记录
rows, err = query.ListIndex("market_order", prefix, nil, et.Count, direction) rows, err = table.ListIndex("market_order", prefix, nil, et.Count, direction)
} else { } else {
rows, err = query.ListIndex("market_order", prefix, []byte(primaryKey), et.Count, direction) rows, err = table.ListIndex("market_order", prefix, []byte(primaryKey), et.Count, direction)
} }
if err != nil { if err != nil {
elog.Error("findOrderIDListByPrice.", "left", left, "right", right, "price", price, "err", err.Error()) elog.Error("findOrderIDListByPrice.", "left", left, "right", right, "price", price, "err", err.Error())
...@@ -562,19 +556,18 @@ func Direction(op int32) int32 { ...@@ -562,19 +556,18 @@ func Direction(op int32) int32 {
} }
//这里primaryKey当作主键索引来用,首次查询不需要填值 //这里primaryKey当作主键索引来用,首次查询不需要填值
func QueryMarketDepth(localdb dbm.KVDB, left, right *et.Asset, op int32, primaryKey string, count int32) (*et.MarketDepthList, error) { func QueryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, primaryKey string, count int32) (*et.MarketDepthList, error) {
table := NewMarketDepthTable(localdb) table := NewMarketDepthTable(localdb)
query := table.GetQuery(localdb)
prefix := []byte(fmt.Sprintf("%s:%s:%d", left.GetSymbol(), right.GetSymbol(), op)) prefix := []byte(fmt.Sprintf("%s:%s:%d", left.GetSymbol(), right.GetSymbol(), op))
if count == 0 { if count == 0 {
count = et.Count count = et.Count
} }
var rows []*Row var rows []*tab.Row
var err error var err error
if primaryKey == "" { //第一次查询,默认展示最新得成交记录 if primaryKey == "" { //第一次查询,默认展示最新得成交记录
rows, err = query.ListIndex("price", prefix, nil, count, Direction(op)) rows, err = table.ListIndex("price", prefix, nil, count, Direction(op))
} else { } else {
rows, err = query.ListIndex("price", prefix, []byte(primaryKey), count, Direction(op)) rows, err = table.ListIndex("price", prefix, []byte(primaryKey), count, Direction(op))
} }
if err != nil { if err != nil {
elog.Error("QueryMarketDepth.", "left", left, "right", right, "err", err.Error()) elog.Error("QueryMarketDepth.", "left", left, "right", right, "err", err.Error())
...@@ -593,19 +586,18 @@ func QueryMarketDepth(localdb dbm.KVDB, left, right *et.Asset, op int32, primary ...@@ -593,19 +586,18 @@ func QueryMarketDepth(localdb dbm.KVDB, left, right *et.Asset, op int32, primary
} }
//QueryCompletedOrderList //QueryCompletedOrderList
func QueryCompletedOrderList(localdb dbm.KVDB, left, right *et.Asset, primaryKey string, count, direction int32) (types.Message, error) { func QueryCompletedOrderList(localdb dbm.KV, left, right *et.Asset, primaryKey string, count, direction int32) (types.Message, error) {
table := NewCompletedOrderTable(localdb) table := NewCompletedOrderTable(localdb)
query := table.GetQuery(localdb)
prefix := []byte(fmt.Sprintf("%s:%s", left.Symbol, right.Symbol)) prefix := []byte(fmt.Sprintf("%s:%s", left.Symbol, right.Symbol))
if count == 0 { if count == 0 {
count = et.Count count = et.Count
} }
var rows []*Row var rows []*tab.Row
var err error var err error
if primaryKey == "" { //第一次查询,默认展示最新得成交记录 if primaryKey == "" { //第一次查询,默认展示最新得成交记录
rows, err = query.ListIndex("index", prefix, nil, count, direction) rows, err = table.ListIndex("index", prefix, nil, count, direction)
} else { } else {
rows, err = query.ListIndex("index", prefix, []byte(primaryKey), count, direction) rows, err = table.ListIndex("index", prefix, []byte(primaryKey), count, direction)
} }
if err != nil { if err != nil {
elog.Error("QueryCompletedOrderList.", "left", left, "right", right, "err", err.Error()) elog.Error("QueryCompletedOrderList.", "left", left, "right", right, "err", err.Error())
...@@ -627,19 +619,18 @@ func QueryCompletedOrderList(localdb dbm.KVDB, left, right *et.Asset, primaryKey ...@@ -627,19 +619,18 @@ func QueryCompletedOrderList(localdb dbm.KVDB, left, right *et.Asset, primaryKey
} }
//QueryOrderList,默认展示最新的 //QueryOrderList,默认展示最新的
func QueryOrderList(localdb dbm.KVDB, statedb dbm.KV, addr string, status, count, direction int32, primaryKey string) (types.Message, error) { func QueryOrderList(localdb dbm.KV, statedb dbm.KV, addr string, status, count, direction int32, primaryKey string) (types.Message, error) {
table := NewUserOrderTable(localdb) table := NewUserOrderTable(localdb)
query := table.GetQuery(localdb)
prefix := []byte(fmt.Sprintf("%s:%d", addr, status)) prefix := []byte(fmt.Sprintf("%s:%d", addr, status))
if count == 0 { if count == 0 {
count = et.Count count = et.Count
} }
var rows []*Row var rows []*tab.Row
var err error var err error
if primaryKey == "" { //第一次查询,默认展示最新得成交记录 if primaryKey == "" { //第一次查询,默认展示最新得成交记录
rows, err = query.ListIndex("index", prefix, nil, count, direction) rows, err = table.ListIndex("index", prefix, nil, count, direction)
} else { } else {
rows, err = query.ListIndex("index", prefix, []byte(primaryKey), count, direction) rows, err = table.ListIndex("index", prefix, []byte(primaryKey), count, direction)
} }
if err != nil { if err != nil {
elog.Error("QueryOrderList.", "addr", addr, "err", err.Error()) elog.Error("QueryOrderList.", "addr", addr, "err", err.Error())
...@@ -659,7 +650,7 @@ func QueryOrderList(localdb dbm.KVDB, statedb dbm.KV, addr string, status, count ...@@ -659,7 +650,7 @@ func QueryOrderList(localdb dbm.KVDB, statedb dbm.KV, addr string, status, count
return &orderList, nil return &orderList, nil
} }
func queryMarketDepth(localdb dbm.KVDB, left, right *et.Asset, op int32, price float64) (*et.MarketDepth, error) { func queryMarketDepth(localdb dbm.KV, left, right *et.Asset, op int32, price float64) (*et.MarketDepth, error) {
table := NewMarketDepthTable(localdb) 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, int64(Truncate(price)*float64(1e8))))
row, err := table.GetData(primaryKey) row, err := table.GetData(primaryKey)
......
...@@ -280,14 +280,18 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa ...@@ -280,14 +280,18 @@ func (e *exchange) updateIndex(receipt *ety.ReceiptExchange) (kvs []*types.KeyVa
depth, err := queryMarketDepth(e.GetLocalDB(), left, right, op, price) depth, err := queryMarketDepth(e.GetLocalDB(), left, right, op, price)
if err == nil { if err == nil {
//marketDepth //marketDepth
marketDepth.Amount = depth.Amount - receipt.GetOrder().Balance marketDepth.Price = price
marketDepth.LeftAsset = left
marketDepth.RightAsset = right
marketDepth.Op = op
marketDepth.Amount = depth.Amount - order.Balance
err = marketTable.Replace(&marketDepth) err = marketTable.Replace(&marketDepth)
if err != nil { if err != nil {
elog.Error("updateIndex", "marketTable.Replace", err.Error()) elog.Error("updateIndex", "marketTable.Replace", err.Error())
return nil return nil
} }
} }
if marketDepth.Amount == 0 { if marketDepth.Amount <= 0 {
//删除 //删除
err = marketTable.DelRow(&marketDepth) err = marketTable.DelRow(&marketDepth)
if err != nil { if err != nil {
......
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