# 磁力兑换

# 磁力兑换是什么

磁力兑换是MOV协议下的核心产品之一,是将用户的资产通过合约的形式在链上完成挂单,撮合和成交的一种资产交易方式。通俗的说,磁力兑换实现的就是去中心化交易的功能,并且在去中心的同时具备了媲美中心化交易所的速度。
磁力兑换的出现,是MOV协议的的应用落地,磁力兑换是磁力合约的重要使用形式。

# 磁力兑换的核心流程

磁力兑换是磁力合约的使用形式,磁力合约将BUTXO的资产交换能力进一步放大和标准化,通过引入矩阵交易对的模式,可以在一笔交易内就完成资产间矩阵置换,多资产匹配和原子互换更加容易实现。
磁力兑换的核心步骤是链上撮合,事实上目前市面上常见的去中心化交易协议,例如0x,都是链下撮合,链上结算。这是由于以太坊性能不足而妥协的方案。而得益于高速侧链Vapor,MOV能够实现链上撮合,实现真正的去中心化。
简单的说,撮合就是获取用户的订单,并且匹配最优价格,最后把撮合成功后的对应资产转入双方的账户中。
首先从数据结构入手,为了实现链上撮合,定义了几种数据结构:

type MovUtxo struct {
	SourceID       *bc.Hash
	SourcePos      uint64
	Amount         uint64
	ControlProgram []byte
}

MovUtxo是一种专门用于MOV交易的UTXO,相比普通的UTXO,做了一部分简化,把验证相关的字段删去,只保留交易相关的内容。

type Order struct {
	FromAssetID      *bc.AssetID
	ToAssetID        *bc.AssetID
	Utxo             *MovUtxo
	RatioNumerator   int64
	RatioDenominator int64
}

定义了订单的格式FromAssetID,ToAssetID分别定义了要交易的资产,RatioNumerator,RatioDenominator定义了两种资产兑换的价格

type OrderBook struct {
	movStore database.MovStore
	dbOrders *sync.Map
	arrivalAddOrders *sync.Map
	arrivalDelOrders *sync.Map
}

定义了订单簿的格式,将存储在levelDB中的订单载入到内存中,以满足高速匹配的要求。dbOrders记录当前内存中的挂单,如果全部被撮合完了,则需要从movStore数据库中取,arrivalAddOrders,arrivalDelOrders分别是交易池中最新的挂单和撤单。

合约内容

contract MagneticContract(requestedAsset: Asset,
                           ratioNumerator: Integer,
                           ratioDenominator: Integer,
                           sellerProgram: Program,
                           standardProgram: Program,
                           sellerKey: PublicKey) locks valueAmount of valueAsset {
  clause partialTrade(exchangeAmount: Amount) {
   define actualAmount: Integer = exchangeAmount * ratioDenominator / ratioNumerator
   verify actualAmount > 0 && actualAmount < valueAmount
   define receiveAmount: Integer = exchangeAmount * 999 / 1000
   lock receiveAmount of requestedAsset with sellerProgram
   lock valueAmount-actualAmount of valueAsset with standardProgram
   unlock actualAmount of valueAsset
  }
  clause fullTrade() {
   define requestedAmount: Integer = valueAmount * ratioNumerator / ratioDenominator
   define requestedAmount: Integer = requestedAmount * 999 / 1000
   verify requestedAmount > 0
   lock requestedAmount of requestedAsset with sellerProgram
   unlock valueAmount of valueAsset
  }
  clause cancel(sellerSig: Signature) {
   verify checkTxSig(sellerKey, sellerSig)
   unlock valueAmount of valueAsset
  }
 }

磁力合约在MOV协议中,主要负责两件事情:第一是在共识节点打包交易的时候负责将可匹配的UTXO对生成交易传递给链内核层打包入块;第二是再验证区块的时候验证区块内交易的匹配逻辑是否符合规则,防止共识节点再匹配过程中进行作恶。用户所有的挂单和交易匹配均是链上的合约行为。
对于挂单合约的设计,磁力合约的本质目的是锁定任意数量的资产A,愿意以某种特定的汇率兑换资产B。合约的内部保存四个常量:期望兑换的资产B的ID、期望兑换的汇率、挂单用户的公钥、挂单用户接受资产B 的地址。合约可以通过三种模式解决:

  • 全部解决:合约中的所有资产A都被兑换成了资产B并且转入挂单用户的地址
  • 部分解决:合约中部分资产A被兑换成了资产B并转入挂单用户的地址,剩余资产A通过递归合约的模式重新锁回合约本身(新生成的UTXO)
  • 取消挂单:挂单用户通过私钥签名将合约中的 资产A都转回自己的地址

磁力合约状态转换图

撮合细节
当用户挂单后,挂单会进入交易池中,等待被撮合,撮合节点获取所有的挂单,尝试进行撮合,启动的在vapor/application/mov/mov_core.go

func (m *Core) BeforeProposalBlock(txs []*types.Tx, blockHeight uint64, gasLeft int64, isTimeout func() bool) ([]*types.Tx, error) {
	……
	orderBook, err := buildOrderBook(m.movStore, txs)
	……
	……
	matchEngine := match.NewEngine(orderBook, match.NewDefaultFeeStrategy(), rewardProgram)
	tradePairIterator := database.NewTradePairIterator(m.movStore)
	matchCollector := newMatchTxCollector(matchEngine, tradePairIterator, gasLeft, isTimeout)
	return matchCollector.result()
}

然后我们获取交易池中和之前数据库中所有的交易,构造出一个订单簿。然后使用NewEngine方法调起撮合引擎,然后使用newMatchTxCollector来并发撮合流程vapor/application/mov/match/engine.go
首先是HasMatchedTx,来检查是否存在能够匹配的订单

func (e *Engine) HasMatchedTx(tradePairs ...*common.TradePair) bool {
	if err := validateTradePairs(tradePairs); err != nil {
		return false
	}

	orders := e.orderBook.PeekOrders(tradePairs)
	if len(orders) == 0 {
		return false
	}

	return IsMatched(orders)
}

先验证一下是否存在这个交易对,然后获取这个交易对的最优订单,在使用IsMatched方法来检查是否存在可以匹配的订单。

func IsMatched(orders []*common.Order) bool {
   sortedOrders := sortOrders(orders)
   if len(sortedOrders) == 0 {
      return false
   }

   product := big.NewRat(1, 1)
   for _, order := range orders {
      product.Mul(product, big.NewRat(order.RatioNumerator, order.RatioDenominator))
   }
   one := big.NewRat(1, 1)
   return product.Cmp(one) <= 0
}

sMatched方法取出其中第一笔交易获取它的汇率,再初始化一个汇率倒数1,将汇率倒数再和下一笔交易的汇率倒数进行相乘,然后比较两个值大小,如果大于等于0,则说明有订单可以匹配,如果小于0则说明没有。

匹配细节

如果isMatched返回true值,说明存在可以撮合的订单,我们调用NextMatchedTx,首先构建匹配交易,再从orderbook中删除已匹配的交易,如果是部分匹配,则把剩下的订单再放入orderbook中。
真正的实现链上资产成交的函数buildMatchTx

func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
	txData := &types.TxData{Version: 1}
	……
	receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
	allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
	if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets); err != nil {
		return nil, err
	}

	if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Fees); err != nil {
		return nil, err
	}

	byteData, err := txData.MarshalText()
	……

	txData.SerializedSize = uint64(len(byteData))
	return types.NewTx(*txData), nil
}

找出订单对应的utxo,计算交易双方能够获得的资产数量,设置手续费,然后调用 addMatchTxOutput构造交易后的output。
链上资产成交的函数buildMatchTx

func (e *Engine) buildMatchTx(orders []*common.Order) (*types.Tx, error) {
	txData := &types.TxData{Version: 1}
	for _, order := range orders {
		input := types.NewSpendInput(nil, *order.Utxo.SourceID, *order.FromAssetID, order.Utxo.Amount, order.Utxo.SourcePos, order.Utxo.ControlProgram)
		txData.Inputs = append(txData.Inputs, input)
	}

	receivedAmounts, priceDiffs := CalcReceivedAmount(orders)
	allocatedAssets := e.feeStrategy.Allocate(receivedAmounts, priceDiffs)
	if err := addMatchTxOutput(txData, orders, receivedAmounts, allocatedAssets); err != nil {
		return nil, err
	}

	if err := e.addMatchTxFeeOutput(txData, allocatedAssets.Fees); err != nil {
		return nil, err
	}

	byteData, err := txData.MarshalText()
	if err != nil {
		return nil, err
	}

	txData.SerializedSize = uint64(len(byteData))
	return types.NewTx(*txData), nil
}

计算解锁合约需要的各个参数,包括需要给对方的资产数量,自己接受的资产数量,是否是部分交易等等,然后再调用setMatchTxArguments来设置磁力合约中不同的入口来真正解锁链上的UTXO,这样订单就撮合完成了。

# 磁力兑换的特点

# 磁力兑换的优势

急速体验
磁力兑换功能是基于Vapor实现的,为了支持全新的Bystack架构和MOV协议,Vapor全面升级Vapor Pro;集成支持基于BUTXO模型的磁力合约。Vapor采用DPoS共识,每0.5s就可以完成一次出块。因此,使用磁力兑换,只要有匹配用户价格的订单,用户的订单最快可以在0.5s内成交。体感上非常接近中心化的交易所。

资产安全
相比传统中心化交易所的资产托管模式,磁力兑换在使用过程中,用户的资产都是由用户自己掌控,用户不需要将自己的资产托管给交易方,完全不会有资产挪用和被窃取的风险,只需要保管好你的私钥即可。

链上撮合
用户的订单和对手单的订单,通过区块链上进行撮合和结算。所有的信息均在链上可查,公开透明。

费率合理
在兼顾去中心化、交易速度和安全的同时,磁力兑换的费率仅为 0.1%,Taker和Maker费率相同。

交易便捷
对于使用Bycoin交易的普通用户,由于使用磁力兑换是由用户进行买一卖一挂单形成,和中心化的交易类似,用户在使用的过程中没有学习和上手的难度。

磁力兑换交易界面

# 与中心化交易所的区别

交易即转账
用户所有的兑换交易,实际上都是链上的转账,可以到 http://www.bymov.io/data/exchange/ 以及vapor区块链浏览器https://vapor.blockmeta.com/ 通过链上地址查询到市商做市的所有交易。

拥有私钥,即可转走账户上的所有金额
MOV Server和MOV-MMDK不会存储用户的私钥,用户请妥善保管你的私钥。拥有私钥就拥有账户的最高权限,泄露私钥会导致账户上所有资金被人转走,丢失私钥,将失去对账户资金的控制权!

建议一个交易对对应一个钱包,不要在一个钱包并发多笔交易
构建交易订单实际上是发送一笔UTXO交易。流程是这样的:服务器返还需要签名的相关UTXO交易->用户端签名,提交用户订单到服务器。 这时候,如果需要签名的相关UTXO出现重复,则可能导致这笔交易失败。 因此我们建议您采用单线程的方式来执行交易订单请求,如果真的需要并发交易的时候,请尽量通过多个钱包单线程的方式来解决问题

撮合方式不一样,按区块打包撮合,发单价即是成交的价格
因为是链上撮合的机制,不存在maker与taker的关系,所以用户报单的价格,即是实际的成交价格! 对于做市商的订单,如果该订单被其他订单撮合匹配,那么这批撮合订单会在下一个区块打包处理, 否则会一直保留在链上,直到做市商撤单或者在后面的区块中被其他的订单相匹配。
请注意的一点:因为当前撮合引擎的设计,如果两个撮合的订单,如果报价一个是 ( 5.00 , buy ), (4.00, sell),返回给他们实际成交价是(5.00, buy) 以及 (4.00, sell),而不是(4.5,buy), (4.5, sell)。中间的差价部分会被当做手续费奖励给打包区块的节点。
再次强调一次:发单价即是未来可能的成交价,不要发市价单!

Last Updated: 8/31/2020, 10:26:01 AM