Solana生态去中心化交易所Serum源码分析
Serum是一个去中心化交易所和生态系统,为去中心化金融带来前所未有高交易速度和低交易成本,其建立在Solana区块链网络之上,无需第三方许可便可使用。Serum的目标是解决现Defi协议弱中心化、低资金使用率、流动性分割、低交易吞吐量以及交易成本高等问题。Serum在链上搭建了中央订单薄以及撮合引擎,提供了非常高的交易吞吐量以及极低的交易延迟。
Serum 一个完整的交易流程包括以下4个阶段:
1. 下单
用户从自己的SPL钱包转移资金到中间账户OpenOrders,创建订单并提交一个Request给撮合引擎。
2. 撮合
撮合引擎收到Request后进行交易的撮合处理,并把结果更新到Orderbook账户中。将撮合结果放入Event Queue。
3. 结算
从Event Queue中取出撮合阶段产生的Event并做相应的处理,将交易结果更新到用户的OpenOrders账户中。
4. 偿付
用户可随时将所属中间账户OpenOrders中未锁定资金和手续费奖励提取到钱包中。
一、源码结构
1. 代码库
代码git仓库地址:https://github.com/project-serum/serum-dex.git
版本号:bcedad83ece3b898103af3fef5d14c5798a4c4f2
2. 目录介绍
dex/src/libs.rs
包声明,Solana程序入口。dex/src/critbit.rs
Critbit Tree的实现,用于订单的存储管理。Critbit Tree是一种深度为O(longest-length)的树,与二叉树类似,做分支检查的代价非常小。Critbit Tree的特性用来做订单的插入、查找、删除、更新等操作非常高效。dex/src/error.rs
自定义错误。dex/src/fees.rs
根据用户持有的SRM代币计算该用户的手续费级别,并获取对应级别要收取的费率。dex/src/instruction.rs
程序指令相关的代码。主要实现了Market、Order相关指令的创建、unpack、pack。dex/src/matching.rs
实现交易撮合逻辑。包括提交新订单、Ask、Bid交易撮合、取消订单等功能,以及限价单、市价单的撮合逻辑。dex/src/state.rs
状态管理的相关逻辑。主要包括:Market的创建、初始化、管理Order的提交、创建、取消等操作结算、费用、偿付Request Queue、以及Event Queue的逻辑dex/src/tests.rs
演示了一个完整订单流程,该代码可以辅助理解整个项目的架构思路。二、主要账户
Serum通过Solana账户存储链上数据,主要有两类账户:DEX的全局账户以及用户专有账户。
Market: 存储Market的元数据。一个交易对对应一个Market,例如交易对BTC/USDT是一个Market,而SOL/USDT是另一个Market。Base Currency Vault: DEX的资金账户,用于存放基础货币,为Base Token的SPL关联账户。Quote Currency Vault: DEX的资金账户,用于存放报价货币,为Quote Token的SPL关联账户。
说明:交易对BTC/USDT中,BTC为Base Token即基础货币,而USDT为Quote Token即报价货币。Request Queue: 请求队列,环形队列。该帐户存放所有提交但未处理的订单以及取消订单请求。三、 详细流程
创建Market
用户提交InitializeMarket指令创建一个交易对的Market,dex/src/state.rs函数process_initialize_market进行如下处理:
获取账户信息初始化Request Queue初始化Event Queue初始化订单薄存储,按Critbit Tree结构存储根据以上步骤获取的参数,初始化Market账户
如果参数有授权账户,则初始化为v2版Market无授权账户,则初始化为V1版初始化Market时可以指定用户拥有以下特殊权限:
授权用户可以修改OpenOrders账户授权用户可以清空Market的订单授权用户可以发起结算操作
订单流程
1. 下单
用户提交NewOrderV3指令进行下单,指令内容包括:
订单详情:所属Market、订单金额、价格、订单类型、交易方向用户在该Market中的 OpenOrders账户如果是卖单需传入基本货币的账户,买单则传入报价货币的账户指令接收后调用dex/src/state.rs的process_new_order_v3函数处理,流程如下:
计算转入与需要锁定的资金:
确定该订单所需的最大资金,如果是卖出则为订单金额,如果是买入则为金额*价格。检查用户OpenOrders中间帐户中指定的相应货币未锁定的余额是否满足交易金额。将OpenOrders帐户中相应货币的总余额加上从用户账户上转移的金额。从请求队列中增加序列号,根据订单价格与检索序列号生成新订单的ID。发送请求(内容为订单金额、价格、订单类型、交易方向)到撮合引擎进行交易撮合,撮合引擎将交易结果放入Event Queue,等待结算处理。将新订单添加到用户的OpenOrders账户数组的可用solt上。转账,则将所需金额和OpenOrders中间户中未锁定余额之间的差额从SPL账户转移到基础货币库或报价货币库。上面的操作均是原子操作,任何单个步骤失败,则整个交易都会失败。 涉及从用户的SPL帐户转移资金到DEX的SPL资金账户的步骤使用跨程序调用,DEX转移资金需要用户授权后才能转移成功。
订单ID是由请求队列中的序列号和订单价格生存的唯一标识符,它是一个128位的数字,其中前64位是价格,后64位则是序列号(如果是买单,则反转所有位)。订单ID的顺序反映相对价格-时间优先级。
2. 撮合
撮合引擎维护了一个Orderbook结构,使用Critbit Tree分别存储买入、卖出订单,通过下单阶段生成的订单ID能快速地在树结构中找到相应的订单信息。
用户提交NewOrderV3、CancelOrderV2指令都会触发撮合引擎,完成撮合后会生成相关Event放入Event Queue,并等待结算阶段处理。dex/src/matching.rs的process_orderbook_request函数是撮合引擎的入口函数,针对每次撮合将做如下的处理:
新订单:
Bid->买单,则执行买单的撮合操作
强制执行IOC和post-only类型的订单对于任何匹配成功的两个订单,生成两个相应的Fil类型Event并添加到Event Queue中对于未匹配成功的交易以及IOC订单未执行成功被取消的订单,生成Out类型Event添加到Event Queue中Ask->卖单,则执行卖单的撮合操作
强制执行IOC和post-only类型的订单对于任何匹配成功的两个订单,生成两个相应的Fil类型Event并添加到Event Queue中对于未匹配成功的交易以及IOC订单未执行成功被取消的订单,生成Out类型Event添加到Event Queue中取消订单:
根据订单ID在Orderbook的树结构上找到相应的叶子节点,并删除生成Out类型的Event放入Event Queue放入Event Queue中两种类型的Event分别包含如下信息:
Fill:
交易方向(买入还是卖出)交易的发起人是不是Maker(是Maker有手续费奖励)该交易对手支付的数量(如果是买入,该数量为以报价货币计算)该交易对手收到的数量(如果是买入,该数量为以基准货币计算)该交易支付的费用(费用收取的是报价货币)Out:
交易方向(买入还是卖出)需要释放的资金数量剩余锁定在订单中的数量(0时未完全取消,非0时为部分取消)两种Event均包含订单ID和solt以及对应OpenOrders帐户的公钥
3. 结算
客户端监控Event Queue,若有新Event则收集受影响OpenOrders中间账户的公钥,并发送ConsumeEvents指令进行结算处理。该逻辑由dex/src/state.rs的process_consume_event函数实现,流程如下:
使用二分查找到该订单用户的OpenOrders账户,若未找到则处理结束,找到则继续下面的步骤从Event Queue中取出Event并处理:
Fill->撮合成功订单:
从OpenOrders帐户的总余额中减去已支付的数量,根据Event中的数量增加总余额和未锁定余额扣减交易费用,包括Maker奖励的手续费以及交易所收取的费用Out->撮合失败或取消订单:
解锁OpenOrders帐户中锁定的订单额度如果Event的指定锁定数量为零,则从OpenOrders帐户的订单列表中删除该订单。删除处理过的Event,继续下一个Event的处理4. 偿付
用户可发送SettleFunds指令将OpenOrders中未锁定的资金提取到指定的SPL钱包。 dex/src/state.rs函数process_settle_funds进行该指令的处理,流程如下:
从Market的余额中减去要提取的金额从用户的OpenOrders账户中减去相应的金额计算转账要用的seeds,并执行转账操作若要转出奖励手续费,则转出手续费并扣减Market的奖励手续费5. 取消订单
用户发送CancelOrderV2指令取消订单,调用dex/src/matching.rs的cancel_order_v2函数,从Orderbook中将订单删除,并发送取消的Event到Event Queue,执行结算中的Out流程。
6. 资金安全
资金账户:PDA地址,权限由程序控制,不存在被第三方恶意转出风险。
用户资金:用户授权后,程序才能转出用户钱包中的资金,不存在风险。
中间账户:源码中OpenOrders中间户开户的源码已经被删除,无法审计其安全性。若初始化过程中可修改中间户中可用余额,攻击者可以通过偿付流程恶意提取资金账户的资金。
发表回复