Compound学习(一) README.md

Compound学习(一) README.md

Compound 是 DeFi 的明星项目之一,定位于去中心化的借贷协议。可以称之为“去中心化的算法银行”。Compound 协议是为了开放金融系统而为开发者构建的开源协议,基于 Compound 协议可以开发一系列新的金融应用程序。

终于要学习Compound了,躲不过去了。

我们知道,Compound是以太坊上的一个借贷协议,当然和Uniswap一样,也是明星项目。关于Compound的介绍在知乎上有一篇文章,我也是看得这篇文章:https://zhuanlan.zhihu.com/p/150553592,因此我们直接跳过项目介绍这一传统篇章,直接进行学习。

学习一个项目(源码)从哪入手呢?除了刚才提到的介绍(包含白皮书,说明文档),我想应该从项目自带的文档README.md开始。

我们在github上clone一下Compound的master分支到本地,打开项目根目录,会发现一个README.md。顾名思义,读我,那我们就从读它开始了。

Compound在github上的地址为:https://github.com/compound-finance/compound-protocol

README.md

我们使用vscode打开Compound项目根目录,找到README.md并打它开,映入眼帘的是md格式各种控制标签,没关系,我们在右边栏目右键点击README.md标签,再选择打开预览,我们就能看到MD文件的正确显示了。

一、介绍

README.md的第一段是介绍这个Compound项目是做什么的,英文好的同学直接看上面链接的白皮书,不好的同学就看我在开头提到的那篇介绍。

二、Contributing

这个基本和我们没有关系,没有能力贡献这个代码(不排除部分读者可以)。

三、核心合约分类

该MD的第三段是核心合约分类,这个是我们需要着重关注的。

3.1、CToken,CErc20和CEther。

本段第一句的意思为Compound Tokens是自完备的借贷合约。CToken 里面是核心逻辑,而CErc20 和 CEther分别是Erc20和Eth的公共接口。每个CToken都包含了利息和任务模块,它允许用户来提供资产、赎回资产、借资产和归还资产。CToken也完全兼容ERC20,它的余额代表着该市场的份额(这里每种CToken对应一个市场)。

那么它的合约具体有哪些呢?打开左边contracts目录,密密麻麻的很多,不要慌。既然这里提到了三类合约,那么所有以CErc20打头的合约(.sol)及 CToken.solCEther.sol就是本类合约了。

3.2、Comptroller

任务模块合约,它用来验证用户行为权限。当用户不满足特定任务条件时,便会关闭该行为。例如,Comptroller 强制用户在借款时必须持有足够的保证金。

对应的具体合约为所有以Comptroller打头的合约和Unitroller.sol

3.3、Comp

这个很简单,Compound发的币,用来进行治理的。代币合约为Governance目录下的Comp.sol

3.4、Governor Alpha

用来进行运维管理的时间锁定合约。持有Comp的玩家可以发起一项提案或者对已有提案投票。通过后的提案会排队并在一定时间后执行。所有的DAO都是类似的。

对应的合约就是在Governance目录下(这个不是重点学习的 内容)。

3.5、InterestRateModel

利息模块。这个模块根据当前市场的的利用率来计算相应的利息。

注意:左边InterestRateModel.sol为一抽象合约,其中一个实现为WhitePaperInterestRateModel.sol,顾名思义,就是根据最初的白皮书实现的。

3.6、Careful Math

数学库,这个跳过

3.7、ErrorReporter

用不同的返回值代表不同的错误情况,一般0代表成功(这就是一般会验证Compound类合约用户相关操作后的返回值为0的原因)。见左边ErrorReporter.sol

3.8、Exponential

用来处理固定精度十进制数的库,可以跳过。

3.9、SafeToken

用来和Erc20合约安全交互的库。很多DEFI都会用到,也较常见。

3.10、WhitePaperInterestRateModel

上面已经介绍过了,注意这里提到了它的构造器中包含两个参数,一个是基准利率,另一个暂切叫着斜率参数吧,这里先记下来,以后有用的着的。

四、安装依赖库

按提示安装就可以了,这里笔者遇到了使用yarn安装失败的情况,于是使用了npm install

五、REPL、Testing、Code Coverage

主要是测试相关,暂时我们用不上。

六、Linting

这里可以跳过。

七、Docker 与 Console

这部分介绍怎么在Docker部署并显示相关合约,我们挑重点看:

7.1、部署基础合约

找到文档中的Deployed goerli contracts,它会部署如下合约:

comptroller: 0x627EA49279FD0dE89186A58b8758aD02B6Be2867
comp: 0xfa5E1B628EFB17C024ca76f65B45Faf6B3128CA5
governorAlpha: 0x8C3969Dd514B559D78135e9C210F2F773Feadf21
maximillion: 0x73d3F01b8aC5063f4601C7C45DA5Fdf1b5240C92
priceOracle: 0x9A536Ed5C97686988F93C9f7C2A390bF3B59c0ec
priceOracleProxy: 0xd0c84453b3945cd7e84BF7fc53BfFd6718913B71
timelock: 0x25e46957363e16C4e2D5F2854b062475F9f8d287
unitroller: 0x627EA49279FD0dE89186A58b8758aD02B6Be2867

这里列出了8个Compound要部署的合约,虽然只是部分并不完整。

comp: 项目的治理代币合约

comptroller:comptroller的实现合约

unitroller:comptroller的代理合约。左边点击打开comptroller.sol,会在注释里看到这么一句话:

Storage for the comptroller is at this address, while execution is delegated to the `comptrollerImplementation`.
CTokens should reference this contract as their comptroller.

也就是该地址用存储数据,执行委托调用comptroller的实现合约。所有的CToken应该设置comptroller为本合约(Unitroller)。

这里可以看出使用了一种代理委托的可升级模式。

governorAlpha: 提案管理合约,防止使用个人账号进行管理。

timelock:时间锁定合约,成功的提案在锁定一段时间后才会执行,给大家留出准备时间。

maximillion:有来处理CEther的合约

priceOracle:这里打开左边PriceOracle.sol,可以发现它为一个抽象合约,相当于定义了某些接口和数据结构。SimplePriceOracle.sol是它一个简单实现,所以这个地址可以为SimplePriceOracle.sol地址。

priceOracleProxy:项目中没有相应的合约,不过我们可以在CreamFi团队Fork的compound-protocol的合约中找到一个实现参考。它是价格预言机的一个较完善版本,能获取多种DEFI中的代币/LP价格信息。

7.2、部署CToken

前面的命令部分我们可以跳过,我们先看下面这段代码:

npx saddle -n rinkeby script token:deploy '{
  "underlying": "0x577D296678535e4903D59A4C929B718e1D575e0A",
  "comptroller": "$Comptroller",
  "interestRateModel": "$Base200bps_Slope3000bps",
  "initialExchangeRateMantissa": "2.0e18",
  "name": "Compound Kyber Network Crystal",
  "symbol": "cKNC",
  "decimals": "8",
  "admin": "$Timelock"
}'

这里,红色部分就是初始参数了。在这里要提及一个概念,就是每个CErc20/CToken对应一个市场,每个市场其实都有一个底层货币(ERC20),也就是underlying,在本例中,底层代币就是KNC。

这里要提及一下,CErc20/CToken其实也是采用了代理委托这种可升级模式:

代理合约为:CErc20Delegator.sol

实现合约为:CErc20Delegate.sol

接下来我们看一下CErc20Delegator的构造器函数:

/**
 * @notice Construct a new money market
 * @param underlying_ The address of the underlying asset
 * @param comptroller_ The address of the Comptroller
 * @param interestRateModel_ The address of the interest rate model
 * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18
 * @param name_ ERC-20 name of this token
 * @param symbol_ ERC-20 symbol of this token
 * @param decimals_ ERC-20 decimal precision of this token
 * @param admin_ Address of the administrator of this token
 * @param implementation_ The address of the implementation the contract delegates to
 * @param becomeImplementationData The encoded args for becomeImplementation
 */
constructor(address underlying_,
            ComptrollerInterface comptroller_,
            InterestRateModel interestRateModel_,
            uint initialExchangeRateMantissa_,
            string memory name_,
            string memory symbol_,
            uint8 decimals_,
            address payable admin_,
            address implementation_,
            bytes memory becomeImplementationData) public {
    // Creator of the contract is admin during initialization
    admin = msg.sender;

    // First delegate gets to initialize the delegator (i.e. storage contract)
    delegateTo(implementation_, abi.encodeWithSignature("initialize(address,address,address,uint256,string,string,uint8)",
                                                        underlying_,
                                                        comptroller_,
                                                        interestRateModel_,
                                                        initialExchangeRateMantissa_,
                                                        name_,
                                                        symbol_,
                                                        decimals_));

    // New implementations always get set via the settor (post-initialize)
    _setImplementation(implementation_, false, becomeImplementationData);

    // Set the proper admin now that initialization is done
    admin = admin_;
}

这里需要提供的构造器参数为10个,上面红色部署为8个,少了实现合约地址和初始编码数据。

由此可见,我们需要先部署一个实现合约,也就是CErc20Delegate.sol,这里只用部署一次就可以了,因为委托合约不包含数据嘛,只负责逻辑。这样我们就得到了缺少的第一个参数:address implementation_

CErc20Delegate.sol合约中的_becomeImplementation函数我们可以看到,那个becomeImplementationData其实是没有用到的,因此我们简单的将它输入0x即可。

这样,10个参数我们就全部拿到了,我们可以部署CToken合约了(记住这里comptroller是Unitroller合约的地址,前面讲过的委托代理可升级模式)。

建议读者可以在以太坊Kovan测试网上自己尝试部署一下Compound,不过,这里还要做一些准备工作,把一些常用的ERC20代币从以太坊主网上搬到Kovan测试上去(重新部署一次),例如USDT、DAI、WETH等。部署几个测试用就可以了。

我们可以多部署几个CToken合约,把常用的代币例如USDT,USDC,DAI之类的市场全部建立起来。记住,实现合约只用部署一次,那个实现地址参数都是相同的。

具体部署的过程下次再研究了。

对于Compound的学习必须先掌握Solidity合约的委托代理这种模式,切记!

八、写在最后

Compound合约第一眼看上去多又复杂,笔者也忍不住打了退堂鼓。然而学习就是一个从难到易的过程,只有用功夫去努力学习,你才能慢慢理解它的结构和细节,最终你会觉得也许并没有那么难。

自己也是边学边写,欢迎读者留言指正错误或者提出改进建议。

上一篇:Typora 上传笔记到博客园


下一篇:白嫖Github部署自己网站