基于以太坊的交易所BANCOR算法实现-转换算法框架

基于以太坊的交易所BANCOR算法实现

## 1,摘要 EOS带火了Bancor算法。其实BANCOR算法是解决长尾通证流通的最好方式之一。鉴于目前通证大部分是基于ERC20的,辉哥正在尝试实现一个使用SOLIDITY语言实现的去中心化交易所,把项目信息整理分享出来。 【本文目标】 (1)学习BANCOR通证转换合约的类图 (2)学习BANCOR通证核心函数 (3)获取源码和注释 ## 2,BANCOR算法功能描述 在以太坊上发行的大量ERC20-Token是没有价值锚定的,其价值完全依赖于项目方的技术与运营能力,若项目失败了,则通证(TOKEN)价值就很可能归零。 若利用智能合约的强大而灵活的“资金流转控制”能力,在通证合约中控制着一定量的储备金,让通证与储备金之间拥有一定的兑换能力,那么Token的价值就可以储备金为锚定物,而不完全依赖于项目方。通证持有者也就不用承担项目失败或者项目方可能诈骗跑路的风险。 若通证与锚定物之间的兑换算法采用了Bancor算法,又符合ERC20标准,则被称为智能通证(Smart-Token) 。为了简单起见,以下的论述以ETH作为锚定物举例说明。购买与售卖Token的过程如下: * “购买者”发送一定量的ETH到Token合约地址,触发了合约代码自动执行"购买功能代码",获得对应数量的Token; * “售卖者”发送一定量的Token到Token合约地址,触发了合约代码自动执行“售卖功能代码”,获得对应数量的ETH。 若AToken与BToken都是以ETH为锚定物的智能通证,那么Token持有者无需通过交易所,仅仅凭借智能合约提供的买卖与兑换功能,就能实现AToken与BToken的自由兑换,比如AToken-->ETH-->BToken,多种智能通证之间通过共同的锚定物串接起来,就形成了一个价值网络(Bancor Network)。 鉴于篇幅限制,本文不准备上传完整源码文件,有需要的同学可加入知识星球下载。 ![](https://img.learnblockchain.cn/2020/02/01_/740383456.png) **【备注】** 加入辉哥知识星球可申请加入区块链交流专项微信群,及时交流各种区块链技术问题。 ## 3,类图分析 ![](https://img.learnblockchain.cn/2020/02/01_/215412379.png) BANCOR转换算法框架类图.png 【核心智能合约简单描述】 **1,contract BancorConverter** **功能说明** :代币转换器,允许一个智能代币和其他代币之间的转换,ERC20连接器的余额可以是虚拟的,从而不需要依赖于真实的余额,这有助于避免在一个协约中有大量金额的风险。转换器可以升级。 **2,ITokenConverter** **功能说明** :BancorConverter的父类接口之一,EIP228 Token Converter接口,用于智能代币的买卖和数量计算接口。 **3,SmartTokenController** **功能说明** :BancorConverter的父类接口之一,智能代币管理器。智能代币管理器是一个可以升级的模块,从而允许更多功能和问题修复。当它接受了代币的所有权,它会成为代币的唯一管理器,执行各个功能。 **4,Managed** **功能说明** : BancorConverter的父类之一,提供协议管理的支持。 **5,IBancorConverterExtensions** **功能说明** :BancorConverter的公开变量类,bancor converter extensions 协议。能返回formula,gasPriceLimit,quickConverter等3类接口合约。 ## 4,核心函数分析 ### 4.1 convert(...)函数 * **convert** (IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) * **功能** : 将一定数量的_fromToken 转换为 _toToken; * **流程图:** ![](https://img.learnblockchain.cn/2020/02/01_/467247055.png) convert函数.png * **源码:** ``` /** @dev 将一定数量的_fromToken 转换为 _toToken @param _fromToken 用来转换ERC20代币 @param _toToken 被转换到的ERC20代币 @param _amount 转换的数量,基于fromToken @param _minReturn 限制转换的结果需要高于minReturn,否则取消 @return conversion 返回数量 */ function convert(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) { convertPath = [_fromToken, token, _toToken]; return quickConvert(convertPath, _amount, _minReturn); } /** @dev 通过之前定义的转换路径来转换代币 注意:当从ERC20代币进行转换,需要提前设置补贴 @param _path 转换路径 @param _amount 转换的数量 @param _minReturn 限制转换的结果需要高于minReturn,否则取消 @return 返回数量 */ quickConvertfunction quickConvert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn) public payable validConversionPath(_path) returns (uint256) { return quickConvertPrioritized(_path, _amount, _minReturn, 0x0, 0x0, 0x0, 0x0, 0x0); } /** @dev 通过之前定义的转换路径来转换代币 注意:当从ERC20代币进行转换,需要提前设置补贴 @param _path 转换路径 @param _amount 转换的数量 @param _minReturn 限制转换的结果需要高于minReturn,否则取消 @param _block 如果当前的区块超过了参数,则取消 @param _nonce 发送者地址的nonce @param _v 通过交易签名提取 @param _r 通过交易签名提取 @param _s 通过交易签名提取 @return 返回数量 */ function quickConvertPrioritized(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, uint256 _block, uint256 _nonce, uint8 _v, bytes32 _r, bytes32 _s) public payable validConversionPath(_path) returns (uint256) { IERC20Token fromToken = _path[0]; IBancorQuickConverter quickConverter = extensions.quickConverter(); // 我们需要从调用者向快速转换着把源代币转化 // 因此他能基于调用者进行转换 if (msg.value == 0) { // 如果不是ETH,把源代币发给快速调用者 // 如果是智能代币,不需要补贴 —— 销毁代币,然后发给快速转换者 if (fromToken == token) { token.destroy(msg.sender, _amount); // 销毁调用者的_amount代币 token.issue(quickConverter, _amount); // 把_amount的新代币发给快速转换者 } else { // 否则,我们假设有了补贴,发给快速转换者 assert(fromToken.transferFrom(msg.sender, quickConverter, _amount)); } } // 执行转换,把ETH转回 return quickConverter.convertForPrioritized.value(msg.value)(_path, _amount, _minReturn, msg.sender, _block, _nonce, _v, _r, _s); } ``` ### 4.2 change(...)函数 * ** function change**(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) * **功能** : 将一定数量的_fromToken 转换为 _toToken。弃用了,向后兼容。设计思路和源码结构挺好的,我画了流程图。 * **流程图:** ![](https://img.learnblockchain.cn/2020/02/01_/712310603.png) BANCOR算法-change函数1.png ![](https://img.learnblockchain.cn/2020/02/01_/449838807.png) BANCOR算法-change函数2.png * **代码:** ``` 既然是过期代码,源代码就不放了。 ``` ### 4.3 getReturn(...)函数 * function **getReturn** (IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256) * **功能** : 返回从一个代币转换为另一个代币的预期数量 * **流程图:** ![](https://img.learnblockchain.cn/2020/02/01_/447638271.png) getReturn ![](https://img.learnblockchain.cn/2020/02/01_/686617199.png) getPurchaseReturn ![](https://img.learnblockchain.cn/2020/02/01_/283911647.png) getSaleReturn * **源码:** ``` /** @dev 返回从一个代币转换为另一个代币的预期数量 @param _fromToken ERC20 被转换的代币 @param _toToken ERC20 转换成的代币 @param _amount 转换的数量 @return 与其转换的数量 */ function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256) { require(_fromToken != _toToken); // 验证输入 // 基于当前代币转换 if (_toToken == token) return getPurchaseReturn(_fromToken, _amount); else if (_fromToken == token) return getSaleReturn(_toToken, _amount); // 在两个连接器之间转换 uint256 purchaseReturnAmount = getPurchaseReturn(_fromToken, _amount); return getSaleReturn(_toToken, purchaseReturnAmount, safeAdd(token.totalSupply(), purchaseReturnAmount)); } /** @dev 返回通过一个连接器代币购买代币的预期结果 @param _connectorToken 连接器代币协约地址 @param _depositAmount 买入的数量 @return 预期的数量 */ function getPurchaseReturn(IERC20Token _connectorToken, uint256 _depositAmount) public view active validConnector(_connectorToken) returns (uint256) { Connector storage connector = connectors[_connectorToken]; require(connector.isPurchaseEnabled); // validate input uint256 tokenSupply = token.totalSupply(); uint256 connectorBalance = getConnectorBalance(_connectorToken); uint256 amount = extensions.formula().calculatePurchaseReturn(tokenSupply, connectorBalance, connector.weight, _depositAmount); // 扣除费用 uint256 feeAmount = getConversionFeeAmount(amount); return safeSub(amount, feeAmount); } /** @dev 返回通过一个连接器代币卖出代币的预期结果 @param _connectorToken 连接器代币协约地址 @param _sellAmount 卖出的数量 @return 预期得到的数量 */ function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount) public view returns (uint256) { return getSaleReturn(_connectorToken, _sellAmount, token.totalSupply()); } /** @dev 工具,基于一个总供应量,返回基于一个连接器代币来卖掉代币的期待返回 @param _connectorToken 连接器代币协议地址 @param _sellAmount 销售的数量 @param _totalSupply 设置总供应量 @return 返回的数量 */ function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount, uint256 _totalSupply) private view active validConnector(_connectorToken) greaterThanZero(_totalSupply) returns (uint256) { Connector storage connector = connectors[_connectorToken]; uint256 connectorBalance = getConnectorBalance(_connectorToken); uint256 amount = extensions.formula().calculateSaleReturn(_totalSupply, connectorBalance, connector.weight, _sellAmount); // 从返回的数量中剪掉费用 uint256 feeAmount = getConversionFeeAmount(amount); return safeSub(amount, feeAmount); } ``` ## 5,未完待续 上述代码完成了智能代币和连接代币转化的关系,但是没有涉及核心互换及计算代码,而是通过interface类的方式进行隔离。这个在另外一个课程进行讲解。 * * * 从白皮书,算法公式验证到代码实现,辉哥从技术穿刺的角度讲透了BANCOR算法在以太坊环境的实现。 辉哥整理了BANCOR的系列知识分享,列表如下: (1) [【白皮书】Bancor协议:通过智能合约为加密货币提供持续流动性(附PDF下载)](https://www.jianshu.com/p/cbfb9abf8e07) (2) [【易错概念】以实例形式深入浅出讲透BANCOR算法](https://learnblockchain.cn/2018/09/17/fe48ed1f38cd) (3) [第二十四课 基于以太坊的交易所BANCOR算法实现-转换算法框架](https://learnblockchain.cn/2018/09/21/875ba83002dc) (4) [第二十五课 如何开发自己的BANCOR去中心化交易平台?](https://learnblockchain.cn/2018/09/30/9fc78f8f5773) 辉哥整理了BANCOR的系列知识分享,列表如下: (1) [【白皮书】Bancor协议:通过智能合约为加密货币提供持续流动性(附PDF下载)](https://www.jianshu.com/p/cbfb9abf8e07) (2) [【易错概念】以实例形式深入浅出讲透BANCOR算法](https://learnblockchain.cn/2018/09/17/fe48ed1f38cd) (3) [第二十四课基于以太坊的交易所BANCOR算法实现-转换算法框架](https://learnblockchain.cn/2018/09/21/875ba83002dc) (4) [第二十五课如何开发自己的BANCOR去中心化交易平台?](https://learnblockchain.cn/2018/09/30/9fc78f8f5773) (5) [第二十七课如何从BANCOR.NETWORK去中心化交易所兑换ENJIN通证](https://learnblockchain.cn/2018/10/11/617103393dc6) 如有需要的同学下载测试工程的可加入知识星球下载。 ![](https://img.learnblockchain.cn/2020/02/01_/965012551.png)

1,摘要

EOS带火了Bancor算法。其实BANCOR算法是解决长尾通证流通的最好方式之一。鉴于目前通证大部分是基于ERC20的,辉哥正在尝试实现一个使用SOLIDITY语言实现的去中心化交易所,把项目信息整理分享出来。 【本文目标】 (1)学习BANCOR通证转换合约的类图 (2)学习BANCOR通证核心函数 (3)获取源码和注释

2,BANCOR算法功能描述

在以太坊上发行的大量ERC20-Token是没有价值锚定的,其价值完全依赖于项目方的技术与运营能力,若项目失败了,则通证(TOKEN)价值就很可能归零。 若利用智能合约的强大而灵活的“资金流转控制”能力,在通证合约中控制着一定量的储备金,让通证与储备金之间拥有一定的兑换能力,那么Token的价值就可以储备金为锚定物,而不完全依赖于项目方。通证持有者也就不用承担项目失败或者项目方可能诈骗跑路的风险。 若通证与锚定物之间的兑换算法采用了Bancor算法,又符合ERC20标准,则被称为智能通证(Smart-Token) 。为了简单起见,以下的论述以ETH作为锚定物举例说明。购买与售卖Token的过程如下:

  • “购买者”发送一定量的ETH到Token合约地址,触发了合约代码自动执行"购买功能代码",获得对应数量的Token;
  • “售卖者”发送一定量的Token到Token合约地址,触发了合约代码自动执行“售卖功能代码”,获得对应数量的ETH。

若AToken与BToken都是以ETH为锚定物的智能通证,那么Token持有者无需通过交易所,仅仅凭借智能合约提供的买卖与兑换功能,就能实现AToken与BToken的自由兑换,比如AToken-->ETH-->BToken,多种智能通证之间通过共同的锚定物串接起来,就形成了一个价值网络(Bancor Network)。 鉴于篇幅限制,本文不准备上传完整源码文件,有需要的同学可加入知识星球下载。

【备注】 加入辉哥知识星球可申请加入区块链交流专项微信群,及时交流各种区块链技术问题。

3,类图分析

BANCOR转换算法框架类图.png

【核心智能合约简单描述】 1,contract BancorConverter 功能说明 :代币转换器,允许一个智能代币和其他代币之间的转换,ERC20连接器的余额可以是虚拟的,从而不需要依赖于真实的余额,这有助于避免在一个协约中有大量金额的风险。转换器可以升级。 2,ITokenConverter 功能说明 :BancorConverter的父类接口之一,EIP228 Token Converter接口,用于智能代币的买卖和数量计算接口。 3,SmartTokenController 功能说明 :BancorConverter的父类接口之一,智能代币管理器。智能代币管理器是一个可以升级的模块,从而允许更多功能和问题修复。当它接受了代币的所有权,它会成为代币的唯一管理器,执行各个功能。 4,Managed 功能说明 : BancorConverter的父类之一,提供协议管理的支持。 5,IBancorConverterExtensions 功能说明 :BancorConverter的公开变量类,bancor converter extensions 协议。能返回formula,gasPriceLimit,quickConverter等3类接口合约。

4,核心函数分析

4.1 convert(...)函数

  • convert (IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256)

  • 功能 : 将一定数量的_fromToken 转换为 _toToken;

  • 流程图:

    convert函数.png

  • 源码:

/**
        @dev 将一定数量的_fromToken 转换为 _toToken

        @param _fromToken  用来转换ERC20代币
        @param _toToken    被转换到的ERC20代币
        @param _amount     转换的数量,基于fromToken
        @param _minReturn  限制转换的结果需要高于minReturn,否则取消

        @return conversion 返回数量
    */
    function convert(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256) {
            convertPath = [_fromToken, token, _toToken];
            return quickConvert(convertPath, _amount, _minReturn);
    }

/**
        @dev 通过之前定义的转换路径来转换代币
        注意:当从ERC20代币进行转换,需要提前设置补贴

        @param _path        转换路径
        @param _amount      转换的数量
        @param _minReturn   限制转换的结果需要高于minReturn,否则取消

        @return 返回数量
    */
    quickConvertfunction quickConvert(IERC20Token[] _path, uint256 _amount, uint256 _minReturn)
        public
        payable
        validConversionPath(_path)
        returns (uint256)
    {
        return quickConvertPrioritized(_path, _amount, _minReturn, 0x0, 0x0, 0x0, 0x0, 0x0);
    }

/**
        @dev 通过之前定义的转换路径来转换代币
        注意:当从ERC20代币进行转换,需要提前设置补贴

        @param _path        转换路径
        @param _amount      转换的数量
        @param _minReturn   限制转换的结果需要高于minReturn,否则取消
        @param _block       如果当前的区块超过了参数,则取消
        @param _nonce       发送者地址的nonce
        @param _v           通过交易签名提取
        @param _r           通过交易签名提取
        @param _s           通过交易签名提取

        @return 返回数量
    */
    function quickConvertPrioritized(IERC20Token[] _path, uint256 _amount, uint256 _minReturn, uint256 _block, uint256 _nonce, uint8 _v, bytes32 _r, bytes32 _s)
        public
        payable
        validConversionPath(_path)
        returns (uint256)
    {
        IERC20Token fromToken = _path[0];
        IBancorQuickConverter quickConverter = extensions.quickConverter();

        // 我们需要从调用者向快速转换着把源代币转化
        // 因此他能基于调用者进行转换
        if (msg.value == 0) {
            // 如果不是ETH,把源代币发给快速调用者
            // 如果是智能代币,不需要补贴 —— 销毁代币,然后发给快速转换者
            if (fromToken == token) {
                token.destroy(msg.sender, _amount); // 销毁调用者的_amount代币
                token.issue(quickConverter, _amount); // 把_amount的新代币发给快速转换者
            } else {
                // 否则,我们假设有了补贴,发给快速转换者
                assert(fromToken.transferFrom(msg.sender, quickConverter, _amount));
            }
        }

        // 执行转换,把ETH转回
        return quickConverter.convertForPrioritized.value(msg.value)(_path, _amount, _minReturn, msg.sender, _block, _nonce, _v, _r, _s);
    }

4.2 change(...)函数

  • function change(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount, uint256 _minReturn) public returns (uint256)

  • 功能 : 将一定数量的_fromToken 转换为 _toToken。弃用了,向后兼容。设计思路和源码结构挺好的,我画了流程图。

  • 流程图:

    BANCOR算法-change函数1.png

    BANCOR算法-change函数2.png

  • 代码:

既然是过期代码,源代码就不放了。

4.3 getReturn(...)函数

  • function getReturn (IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256)

  • 功能 : 返回从一个代币转换为另一个代币的预期数量

  • 流程图:

    getReturn

getPurchaseReturn

getSaleReturn

  • 源码:
/**
        @dev 返回从一个代币转换为另一个代币的预期数量

        @param _fromToken  ERC20 被转换的代币
        @param _toToken    ERC20 转换成的代币
        @param _amount     转换的数量

        @return 与其转换的数量
    */
    function getReturn(IERC20Token _fromToken, IERC20Token _toToken, uint256 _amount) public view returns (uint256) {
        require(_fromToken != _toToken); // 验证输入

        // 基于当前代币转换
        if (_toToken == token)
            return getPurchaseReturn(_fromToken, _amount);
        else if (_fromToken == token)
            return getSaleReturn(_toToken, _amount);

        // 在两个连接器之间转换
        uint256 purchaseReturnAmount = getPurchaseReturn(_fromToken, _amount);
        return getSaleReturn(_toToken, purchaseReturnAmount, safeAdd(token.totalSupply(), purchaseReturnAmount));
    }

/**
        @dev 返回通过一个连接器代币购买代币的预期结果

        @param _connectorToken  连接器代币协约地址
        @param _depositAmount   买入的数量

        @return 预期的数量
    */
    function getPurchaseReturn(IERC20Token _connectorToken, uint256 _depositAmount)
        public
        view
        active
        validConnector(_connectorToken)
        returns (uint256)
    {
        Connector storage connector = connectors[_connectorToken];
        require(connector.isPurchaseEnabled); // validate input

        uint256 tokenSupply = token.totalSupply();
        uint256 connectorBalance = getConnectorBalance(_connectorToken);
        uint256 amount = extensions.formula().calculatePurchaseReturn(tokenSupply, connectorBalance, connector.weight, _depositAmount);

        // 扣除费用
        uint256 feeAmount = getConversionFeeAmount(amount);
        return safeSub(amount, feeAmount);
    }

/**
        @dev 返回通过一个连接器代币卖出代币的预期结果

        @param _connectorToken  连接器代币协约地址
        @param _sellAmount      卖出的数量

        @return 预期得到的数量
    */
    function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount) public view returns (uint256) {
        return getSaleReturn(_connectorToken, _sellAmount, token.totalSupply());
    }

/**
        @dev 工具,基于一个总供应量,返回基于一个连接器代币来卖掉代币的期待返回

        @param _connectorToken  连接器代币协议地址
        @param _sellAmount      销售的数量
        @param _totalSupply     设置总供应量

        @return 返回的数量
    */
    function getSaleReturn(IERC20Token _connectorToken, uint256 _sellAmount, uint256 _totalSupply)
        private
        view
        active
        validConnector(_connectorToken)
        greaterThanZero(_totalSupply)
        returns (uint256)
    {
        Connector storage connector = connectors[_connectorToken];
        uint256 connectorBalance = getConnectorBalance(_connectorToken);
        uint256 amount = extensions.formula().calculateSaleReturn(_totalSupply, connectorBalance, connector.weight, _sellAmount);

        // 从返回的数量中剪掉费用
        uint256 feeAmount = getConversionFeeAmount(amount);
        return safeSub(amount, feeAmount);
    }

5,未完待续

上述代码完成了智能代币和连接代币转化的关系,但是没有涉及核心互换及计算代码,而是通过interface类的方式进行隔离。这个在另外一个课程进行讲解。

从白皮书,算法公式验证到代码实现,辉哥从技术穿刺的角度讲透了BANCOR算法在以太坊环境的实现。

辉哥整理了BANCOR的系列知识分享,列表如下: (1) 【白皮书】Bancor协议:通过智能合约为加密货币提供持续流动性(附PDF下载) (2) 【易错概念】以实例形式深入浅出讲透BANCOR算法 (3) 第二十四课 基于以太坊的交易所BANCOR算法实现-转换算法框架 (4) 第二十五课 如何开发自己的BANCOR去中心化交易平台? 辉哥整理了BANCOR的系列知识分享,列表如下: (1) 【白皮书】Bancor协议:通过智能合约为加密货币提供持续流动性(附PDF下载) (2) 【易错概念】以实例形式深入浅出讲透BANCOR算法 (3) 第二十四课基于以太坊的交易所BANCOR算法实现-转换算法框架 (4) 第二十五课如何开发自己的BANCOR去中心化交易平台? (5) 第二十七课如何从BANCOR.NETWORK去中心化交易所兑换ENJIN通证

如有需要的同学下载测试工程的可加入知识星球下载。

  • 发表于 2018-09-21 17:05
  • 阅读 ( 2644 )
  • 学分 ( 5 )
  • 分类:DeFi

评论