Bancor协议源码分析

1.编译源码、运行测试脚本

  • pull Bancor源码到当前目录。
  • 在当前目录执行npm install 安装依赖的包
  • 进入scripts目录,修改三个js文件中前面几行代码中的相对路径,如“./solidity”改为“…/solidity”。否则会提示“Error: spawn node ENOENT”错误。
  • 在scripts目录执行node rebuild-all.js,编译源代码
  • 在scripts目录执行node run-test.js,运行测试脚本
    在这里插入图片描述
    等待一段时间后,便能够看到类似上图的测试结果

2.代码结构分析

抛开Bancor的模式创新,单从技术上来讲,他已经是以太坊上规模比较大的项目了:将近四十个合约(包括基类合约),最终部署的合约也多达18个合约。
先看Bancor项目的目录结构:

|-- scripts
	|-- fix-modules.js
	|-- rebuild-all.js
	|-- run-tests.js
|-- solidity
	|-- contracts
	|-- migrations
	|-- python
	|-- test
	|-- truffle.js
	|-- truffle-config.js

scripts目录下放的是辅助编译、测试的js脚本文件,可以用这里的文件实现一键测试
solidity目录是一个标准的truffle工程目录。其中test目录里针对每个sol都有一个与之对应的js测试文件,文件里的测试用例非常详细,例如,针对BancorConverter合约的构造函数的测试用例:

合约的构造函数
constructor(
        ISmartToken _token,
        IContractRegistry _registry,
        uint32 _maxConversionFee,
        IERC20Token _connectorToken,
        uint32 _connectorWeight
    )
        public
        SmartTokenController(_token)
        validAddress(_registry)
        validMaxConversionFee(_maxConversionFee)
    {
        registry = _registry;
        prevRegistry = _registry;
        IContractFeatures features = IContractFeatures(registry.addressOf(ContractIds.CONTRACT_FEATURES));

        // initialize supported features
        if (features != address(0))
            features.enableFeatures(FeatureIds.CONVERTER_CONVERSION_WHITELIST, true);

        maxConversionFee = _maxConversionFee;

        if (_connectorToken != address(0))
            addConnector(_connectorToken, _connectorWeight, false);
    }
针对构造函数的测试用例
	it('verifies the converter data after construction', async () => {
        let converter = await BancorConverter.new(tokenAddress, contractRegistry.address, 0, '0x0', 0);
        let token = await converter.token.call();
        assert.equal(token, tokenAddress);
        let registry = await converter.registry.call();
        assert.equal(registry, contractRegistry.address);

        let featureWhitelist = await converter.CONVERTER_CONVERSION_WHITELIST.call();
        let isSupported = await contractFeatures.isSupported.call(converter.address, featureWhitelist);
        assert(isSupported);

        let maxConversionFee = await converter.maxConversionFee.call();
        assert.equal(maxConversionFee, 0);
        let conversionsEnabled = await converter.conversionsEnabled.call();
        assert.equal(conversionsEnabled, true);
    });

    it('should throw when attempting to claim tokens when not enabled', async () => {
        let converter = await initConverter(accounts, true);
        try {
            await converter.claimTokens(accounts[0], 1);
            assert(false, "didn't throw");
        } catch(error) {
            return utils.ensureException(error);
        }
    });
    it('should throw when attempting to construct a converter with no token', async () => {
        try {
            await BancorConverter.new('0x0', contractRegistry.address, 0, '0x0', 0);
            assert(false, "didn't throw");
        }
        catch (error) {
            return utils.ensureException(error);
        }
    });

    it('should throw when attempting to construct a converter with no contract registry', async () => {
        try {
            await BancorConverter.new(tokenAddress, '0x0', 0, '0x0', 0);
            assert(false, "didn't throw");
        }
        catch (error) {
            return utils.ensureException(error);
        }
    });

    it('should throw when attempting to construct a converter with invalid max fee', async () => {
        try {
            await BancorConverter.new(tokenAddress, contractRegistry.address, 1000000000, '0x0', 0);
            assert(false, "didn't throw");
        }
        catch (error) {
            return utils.ensureException(error);
        }
    });

    it('verifies the first connector when provided at construction time', async () => {
        let converter = await BancorConverter.new(tokenAddress, contractRegistry.address, 0, connectorToken.address, 200000);
        let connectorTokenAddress = await converter.connectorTokens.call(0);
        assert.equal(connectorTokenAddress, connectorToken.address);
        let connector = await converter.connectors.call(connectorTokenAddress);
        verifyConnector(connector, true, true, 200000, false, 0);
    });

每个测试用例都通过new一个新的实例来测试指定的功能,该方法只适合虚拟机环境下。其中多个用例还对revert进行了测试,十分细致。

3.业务流程分析

bancor相当于一个去中心化的做市商,用于解决小币种的流动性问题。具体的可参见白皮书。这里从代码角度分析Bancor的业务流程。
最重要的合约便是BancorConverter合约,它首先是一个SmartTokenController,即:该合约可以控制一个smartToken的issue与destroy,另外,它还有一个connectors数组,里面会有一个或多个connectorToken,每个connectorToken在添加到BancorConverter中之后,都需要将一定量的connectorToken转给BancorConverter作为准备金,准备金的数量以及connectorToken的权重直接决定了connectorToken与其他token的兑换比例。

通过测试代码看一下BancorConverter的初始化:

async function initConverter(accounts, activate, maxConversionFee = 0) {
    token = await SmartToken.new('Token1', 'TKN1', 2);
    tokenAddress = token.address;

    let converter = await BancorConverter.new(
        tokenAddress,
        contractRegistry.address,
        maxConversionFee,
        connectorToken.address,
        250000
    );
    let converterAddress = converter.address;
    await converter.addConnector(connectorToken2.address, 150000, false);

    await token.issue(accounts[0], 20000);
    转准备金
    await connectorToken.transfer(converterAddress, 5000);
    await connectorToken2.transfer(converterAddress, 8000);

    if (activate) {
    	将smartToken的控制权完全交给converter
        await token.transferOwnership(converterAddress);
        await converter.acceptTokenOwnership();
    }

    return converter;
}

上述代码创建了一个拥有2个connectorToken的converter,于是,该converter就可以实现这三种代币之间的转换:

在有多个converter的情况下,用户不知道这写converter的地址,于是可以通过指定一个兑换路径(path),使用统一入口:BancorNetwork来进行兑换。

function convertByPath(
        IERC20Token[] _path,
        uint256 _amount,
        uint256 _minReturn,
        IERC20Token _fromToken,
        address _for
    ) private returns (IERC20Token, uint256) {
        ISmartToken smartToken;
        IERC20Token toToken;
        IBancorConverter converter;

        // get the contract features address from the registry
        IContractFeatures features = IContractFeatures(registry.addressOf(ContractIds.CONTRACT_FEATURES));

        // iterate over the conversion path
        uint256 pathLength = _path.length;
        for (uint256 i = 1; i < pathLength; i += 2) {
            smartToken = ISmartToken(_path[i]);
            toToken = _path[i + 1];
            converter = IBancorConverter(smartToken.owner());
            checkWhitelist(converter, _for, features);

            // if the smart token isn't the source (from token), the converter doesn't have control over it and thus we need to approve the request
            if (smartToken != _fromToken)
                ensureAllowance(_fromToken, converter, _amount);

            // make the conversion - if it's the last one, also provide the minimum return value
            _amount = converter.change(_fromToken, toToken, _amount, i == pathLength - 2 ? _minReturn : 1);
            _fromToken = toToken;
        }
        return (toToken, _amount);
    }

path是一个地址数组,举例说明:

tokenA <=> smartTokenX <=> tokenB (converterA)
smartTokenX <=> smartTokenY <=> tokenC (converterB)
想要将手中的tokenA兑换成tokenC, path需要这样填写
[tokenA.address, smartTokenX.address, smartTokenX.address, smartTokenY.address, tokenC.address]

  • 在convertByPath中会先通过smartTokenX.owner查到converterA,
  • 通过converterA将手中的tokenA兑换为smartTokenA,
  • 然后通过smartTokenY.owner 查到converterB,
  • 通过converterB将smartTokenA兑换为tokenC 。
最新觅知扶风视频解析计费系统源码V1.8.2 免授权优化版 附教程 之前有分享过 1.7.1 的扶风计费系统,那个版本很久了之前也一直没有更新,拿到源码之后进行优化,因历史版本的加载和原版的加载速度真的是慢的感人,因此从零开始进行去授权,所有源码均本地化,避免因为外链导致的程序不能正常使用,就对比来说,我个人认为扶风的计费会比云海的比较相对操作好上手,且 bug 也少,而且网商众多的的都是历史版本,因此更新优化一般最新 V1.8 版本免授权版本带,有七套模板 扶风计费程序介绍: 后台可对接多个专用json 接口解析,可以 m3u8 资源站对接配置当所有的解析失效时将会启用资源站解析(你也可以留空其他解析只填写资源站解析) 有支持替换资源系统,也可对接第三方资源站,对接支付宝当面付,支持点数收费模式和包月套餐收费模式,管理可配置多个视频解析接口、短视频接口、备用解析等等常规功能等等! V1.8.2 觅知更新优化记录 新增多个 JSON 接口类型,新增 APP 等其他综合接口对接适应多程序调用 新增一个 DIY 解析接口输出内容可自定义内容,打开/api/diy/index.php 修改第 53 和 54 想显示的文字或连接 新增管理员后台全站调用记录排行记录,可查看解析成功和解析失败的次数,方便及时检查 新增一套前端模板,模板名称:mizhi-seven 修复优化支付宝当面付协议支付成功不回调的问题,已优化至 http 和 https 方式均可正常回调自动适配 优化当面付支付页面,显示充值订单号及充值的账号显示 优化删除每次进入网站时出现的,顶部 done!弹窗 优化前后端后台顶部 LOGO 位置样式 优化用户后台购买套餐时,可无缝切换用户类型,方便查看套餐,不再需要返回首页切换包点或包月用户类型 优化全站接口及域名 http 和 https 协议的问题,自动适配当前域名 SSL 协议 修复优化用户端内置下载免费版弹幕播放器客户端无法播放的问题 优化部分页面快捷入口
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值