Compound 与 cToken 合约 交互

七哥 发布于 2021-01-25 阅读 8350

Compound 协议所支持的每项资产都通过 cToken 合约进行整合,cToken 合约是提供给协议的余额的 EIP-20 合格表示。

请注意,在翻译时遇到一个单词 Underlying 相关的词组有 Underlying BalanceUnderlying assets。个人觉得翻译成基础余额、底层余额、底层资产、基础资产等听着不对味,所以我根据理解,将其翻译为标的余额、标的资产。有来喷的吗?点击右侧加号可以留言!cToken 就是对 Token 的封装,这个 Token 就可以视为交易的标的物。

介绍

Compound 协议所支持的每项资产都通过 cToken 合约进行整合,cToken 合约是提供给协议的余额的 EIP-20 合格表示。通过铸造 cToken,用户(1)通过 cToken 的汇率赚取利息,该汇率相对于标的资产的价值增加,以及(2)获得试用 cToken 作为抵押品的能力。

cToken 是与 Compound 协议交互的主要手段;当用户在使用 cToken 合约时,会使用 cToken 合约来进行铸币(mint)、赎回(redeem)、借款(borrow)、偿还借款(repay)、清算借款(liquidate)或者划转 cToken (transfer)。

目前有两种类型的 cToken : CErc20 和 CEther。尽管两者都暴露了 EIP-20 接口,但 CErc20 封装了 ERC-20 标的资产,而 CEther 只是封装了 Ether 本身。因此,根据类型的不同,涉及将资产传输到协议中的核心功能的接口也略有不同,每种类型的接口如下所示。

铸币(Mint)

mint 方法将一个资产转移到协议中,协议开始根据该资产的当前供应率开始累积利息。用户收到的 cToken 数量等于标的代币数量除以当前汇率

CErc20

1 function mint(uint mintAmount) returns (uint)
  • msg.sender : 供应资产并拥有已铸 cToken 的账户。
  • mintAmount : 供应的资产金额,单位为标的资产的单位。
  • RETURN : 成功时为0,否则是错误代码

供应资产前,用户必须先 批准(approve), cToken 才能访问其代币余额。

CEther

1 function mint() payable
  • msg.value : 供应的 ETH 数量,单位 wei。
  • msg.sender : 供应 ETH 并拥有已铸 cToken 的账户。
  • RETURN : 无返回值,错误时还原。

Solidity

1 Erc20 underlying = Erc20(0xToken...);     // 获取标的资产合约句柄 
2 CErc20 cToken = CErc20(0x3FDA...);        // 获取相应 cToken 合约的句柄 
3 underlying.approve(address(cToken), 100); // 批准划转 
4 assert(cToken.mint(100) == 0);            // 铸 cToken 币,并断言是否有错误

Web3 1.0

1 const cToken = CEther.at(0x3FDB...); 
2 await cToken.methods.mint().send({from: myAccount, value: 50});

赎回(Redeem)

redeem 方法将指定数量的 cToken 转换为标的资产,并将其返还给用户。收到的标的数量等于赎回的 cToken 数量乘以当前汇率。赎回额必须小于用户的账户流动性和市场可用的流动性。

CErc20 / CEther

function redeem(uint redeemTokens) returns (uint)
  • msg.sender : 应将赎回的标的资产转入的账户。
  • redeemTokens : 将被赎回的 cToken 数量。
  • RETURN : 成功时为0,否则是错误代码

Solidity

1 CEther cToken = CEther(0x3FDB...);
2 require(cToken.redeem(7) == 0, "something went wrong");

赎回标的(Redeem Underlying)

redeem underlying 方法将 cToken兑换成指定数量的标的资产,并返回给用户。赎回的 cToken的数量等于收到的标的数量除以当前汇率。赎回额必须小于用户的账户流动性和市场可用的流动性。

CErc20 / CEther

1 function redeemUnderlying(uint redeemAmount) returns (uint)
  • msg.sender : 应将赎回的标的资产转入的账户。
  • redeemAmount : 将被赎回的标的资产数量。
  • RETURN : 成功时为0,否则是错误代码

Solidity

1 CEther cToken = CEther(0x3FDB...);
2 require(cToken.redeemUnderlying(50) == 0, "something went wrong");

Web3 1.0

1 const cToken = CErc20.at(0x3FDA...);
2 cToken.methods.redeemUnderlying(10).send({from: ...});

借款(Borrow)

borrow 方法将协议中的资产转移给用户,并创建一个借款余额,根据该资产的借款利率开始累积利息。借款额必须小于用户的账户流动性和市场可用的流动性。

要想借入以太,借款人必须是 'payable' (Solidity)。

CErc20 / CEther

1 function borrow(uint borrowAmount) returns (uint)
  • msg.sender : 应将借款资金转入的账户。
  • borrowAmount : 标的资产借款数量。
  • ETURN : 成功时为0,否则是错误代码

Solidity

1 CErc20 cToken = CErc20(0x3FDA...);
2 require(cToken.borrow(100) == 0, "got collateral?");

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 await cToken.methods.borrow(50).send({from: 0xMyAccount});

偿还借款(Repay Borrow)

repay 方法将资产转移到协议中,并减少用户的借款余额。

CErc20

1 function repayBorrow(uint repayAmount) returns (uint)
  • msg.sender : 借入资产的账户,应偿还借款。
  • repayAmount : 拟偿还的标的资产借款金额。值为-1(即2256-1)表示偿还全部借款额。
  • RETURN : 成功时为0,否则是错误代码

偿还资产前,用户必须先 批准(approve), cToken 才能访问其代币余额。

CEther

1 function repayBorrow() payable
  • msg.value : 用于偿还的 ETH 数量,单位 wei。
  • msg.sender : 借入资产的账户,应偿还借款。
  • RETURN : 无返回值,错误时还原。

Solidity

1 CEther cToken = CEther(0x3FDB...);

2 require(cToken.repayBorrow.value(100)() == 0, "transfer approved?");

Web3 1.0

1 const cToken = CErc20.at(0x3FDA...);

2 cToken.methods.repayBorrow(10000).send({from: ...});

代偿还借款(Repay Borrow Behalf)

repay 方法将资产转移到协议中,并减少目标用户的借款余额。

CErc20

1 function repayBorrowBehalf(address borrower, uint repayAmount) returns (uint)
  • msg.sender : 偿还借款的账户。
  • borrower : 借入资产要偿还的账户。
  • repayAmount : 拟偿还的标的资产借款金额。值为-1(即2256-1)表示偿还全部借款额。
  • RETURN : 成功时为0,否则是错误代码

偿还资产前,用户必须先 批准(approve), cToken 才能访问其代币余额。

CEther

1 function repayBorrowBehalf(address borrower) payable
  • msg.value : 用于偿还的 ETH 数量,单位 wei。
  • msg.sender : 偿还借款的账户。
  • borrower : 借入资产要偿还的账户。
  • RETURN : 无返回值,错误时还原。

Solidity

1 CEther cToken = CEther(0x3FDB...);
2 require(cToken.repayBorrowBehalf.value(100)(0xBorrower) == 0, "transfer approved?");

Web3 1.0

1 const cToken = CErc20.at(0x3FDA...);
2 await cToken.methods.repayBorrowBehalf(0xBorrower, 10000).send({from: 0xPayer});

清算借款(Liquidate Borrow)

账户流动性为负值的用户,由协议的其他用户进行清算,使其账户流动性恢复到正值(即高于抵押品要求)。当发生清算时,清算人(liquidator)可以代表借款人偿还部分或全部未偿还的借款,作为回报,可以获得借款人持有的抵押品的折价;这种折价被定义为清算奖励。

清算人可将水下账户的任何一个未偿还借款以一定的固定比例(即结算系数)进行结算。与 V1 中不同的是,清算者必须与每一个 cToken 合约中偿还借款并扣押另一项资产作为抵押品的 cToken 进行交互。当抵押品被扣押时,清算人会被转移 cToken,他们可以赎回这些 cToken,就像他们自己提供资产一样。用户必须在调用清算前批准每个 cToken 合约(即在他们要偿还的借款资产上),因为他们正在将资金转移到合约中。

CErc20

1 function liquidateBorrow(address borrower, uint amount, address collateral) returns (uint)
  • msg.sender : 应通过偿还借款人的债务和扣押抵押品来结算借款人的账户。
  • borrower : 应予清算的负账户流动性账户。
  • repayAmount : 借款资产的偿还和转换为抵押品的金额,以标的抵押资产的单位为单位。
  • cTokenCollateral : 清算人应扣押借款人目前作为抵押品持有的 cToken 地址。
  • RETURN : 成功时为0,否则是错误代码

偿还资产前,用户必须先 批准(approve), cToken 才能访问其代币余额。

CEther

1 function liquidateBorrow(address borrower, address cTokenCollateral) payable
  • msg.value : 转换为抵押品的 ETH 金额,单位 wei 。
  • msg.sender : 应通过偿还借款人的债务和扣押抵押品来结算借款人的账户。
  • borrower : 应予清算的负账户流动性账户。
  • cTokenCollateral : 清算人应扣押借款人目前作为抵押品持有的 cToken 地址。
  • RETURN : 成功时为0,否则是错误代码

Solidity

1 CEther cToken = CEther(0x3FDB...); 
2 CErc20 cTokenCollateral = CErc20(0x3FDA...);
3  require(cToken.liquidateBorrow.value(100)(0xBorrower, cTokenCollateral) == 0, "borrower underwater??");

Web3 1.0

1 const cToken = CErc20.at(0x3FDA...); 
2 const cTokenCollateral = CEther.at(0x3FDB...);
3  await cToken.methods.liquidateBorrow(0xBorrower, 33, cTokenCollateral).send({from: 0xLiquidator});

主要事件(Key Events)

Event Description
Mint(address minter, uint mintAmount, uint mintTokens) 铸币成功后发出
Redeem(address redeemer, uint redeemAmount, uint redeemTokens) 赎回成功后发出
Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows) 借款成功后发出
RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows) 偿还借款成功后发出
LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens) 借款清算成功后发出

错误代码(Error Codes)

Code Name Description
0 NO_ERROR 不是失败。
1 UNAUTHORIZED 发件人无权实施这一操作。
2 BAD_INPUT 调用者提供了一个无效参数。
3 COMPTROLLER_REJECTION 该操作将违反审计官政策。
4 COMPTROLLER_CALCULATION_ERROR 审计内部计算失败 。
5 INTEREST_RATE_MODEL_ERROR 利率模型返回了一个无效值。
6 INVALID_ACCOUNT_PAIR 指定的账户组合是无效的。
7 INVALID_CLOSE_AMOUNT_REQUESTED 清算金额无效。
8 INVALID_COLLATERAL_FACTOR 抵押因子无效。
9 MATH_ERROR 出现了数学计算错误。
10 MARKET_NOT_FRESH 利息没有正确产生。
11 MARKET_NOT_LISTED 目前,市场没有由审计员列出。
12 TOKEN_INSUFFICIENT_ALLOWANCE ERC-20 合约必须**允许(allow)**货币市场合约调用transferForm。当前 allow 额为0或小于请求的供给、偿还借款或清算金额。
13 TOKEN_INSUFFICIENT_BALANCE 调用者在 ERC-20 合约中没有足够的余额来完成所需的操作。
14 TOKEN_INSUFFICIENT_CASH 市场上没有足够的现金金额来完成交易。您可以稍后再尝试此项交易。
15 TOKEN_TRANSFER_IN_FAILED 在 ERC-20 代币转入市场时失败。
16 TOKEN_TRANSFER_OUT_FAILED 在 ERC-20 代币转出市场时失败。

失败信息(Failure Info)

Code Name
0 ACCEPT_ADMIN_PENDING_ADMIN_CHECK
1 ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED
2 ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED
3 ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED
4 ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED
5 ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED
6 ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED
7 BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED
8 BORROW_ACCRUE_INTEREST_FAILED
9 BORROW_CASH_NOT_AVAILABLE
10 BORROW_FRESHNESS_CHECK
11 BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED
12 BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED
13 BORROW_MARKET_NOT_LISTED
14 BORROW_COMPTROLLER_REJECTION
15 LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED
16 LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED
17 LIQUIDATE_COLLATERAL_FRESHNESS_CHECK
18 LIQUIDATE_COMPTROLLER_REJECTION
19 LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED
20 LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX
21 LIQUIDATE_CLOSE_AMOUNT_IS_ZERO
22 LIQUIDATE_FRESHNESS_CHECK
23 LIQUIDATE_LIQUIDATOR_IS_BORROWER
24 LIQUIDATE_REPAY_BORROW_FRESH_FAILED
25 LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED
26 LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED
27 LIQUIDATE_SEIZE_COMPTROLLER_REJECTION
28 LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER
29 LIQUIDATE_SEIZE_TOO_MUCH
30 MINT_ACCRUE_INTEREST_FAILED
31 MINT_COMPTROLLER_REJECTION
32 MINT_EXCHANGE_CALCULATION_FAILED
33 MINT_EXCHANGE_RATE_READ_FAILED
34 MINT_FRESHNESS_CHECK
35 MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED
36 MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED
37 MINT_TRANSFER_IN_FAILED
38 MINT_TRANSFER_IN_NOT_POSSIBLE
39 REDEEM_ACCRUE_INTEREST_FAILED
40 REDEEM_COMPTROLLER_REJECTION
41 REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED
42 REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED
43 REDEEM_EXCHANGE_RATE_READ_FAILED
44 REDEEM_FRESHNESS_CHECK
45 REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED
46 REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED
47 REDEEM_TRANSFER_OUT_NOT_POSSIBLE
48 REDUCE_RESERVES_ACCRUE_INTEREST_FAILED
49 REDUCE_RESERVES_ADMIN_CHECK
50 REDUCE_RESERVES_CASH_NOT_AVAILABLE
51 REDUCE_RESERVES_FRESH_CHECK
52 REDUCE_RESERVES_VALIDATION
53 REPAY_BEHALF_ACCRUE_INTEREST_FAILED
54 REPAY_BORROW_ACCRUE_INTEREST_FAILED
55 REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED
56 REPAY_BORROW_COMPTROLLER_REJECTION
57 REPAY_BORROW_FRESHNESS_CHECK
58 REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED
59 REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED
60 REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE
61 SET_COLLATERAL_FACTOR_OWNER_CHECK
62 SET_COLLATERAL_FACTOR_VALIDATION
63 SET_COMPTROLLER_OWNER_CHECK
64 SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED
65 SET_INTEREST_RATE_MODEL_FRESH_CHECK
66 SET_INTEREST_RATE_MODEL_OWNER_CHECK
67 SET_MAX_ASSETS_OWNER_CHECK
68 SET_ORACLE_MARKET_NOT_LISTED
69 SET_PENDING_ADMIN_OWNER_CHECK
70 SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED
71 SET_RESERVE_FACTOR_ADMIN_CHECK
72 SET_RESERVE_FACTOR_FRESH_CHECK
73 SET_RESERVE_FACTOR_BOUNDS_CHECK
74 TRANSFER_COMPTROLLER_REJECTION
75 TRANSFER_NOT_ALLOWED
76 TRANSFER_NOT_ENOUGH
77 TRANSFER_TOO_MUCH

汇率(Exchange Rate)

每个cToken 都可以兑换成数量不断增加的标的资产,因为在市场上会产生利息。cToken 与相关资产之间的汇率等于:

1 exchangeRate = (getCash() + totalBorrows() - totalReserves()) / totalSupply()

CErc20 / CEther

1 function exchangeRateCurrent() returns (uint)
  • 返回值 : 当前的汇率,为无符号整数,按 1e18 缩放;

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint exchangeRateMantissa = cToken.exchangeRateCurrent();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...); 
2 const exchangeRate = (await cToken.methods.exchangeRateCurrent().call()) / 1e18;

请注意,对比 send 使用 call 是在链外调用方法,而不需要导致 Gas 成本。

获取现金(Get Cash)

现金是指该 cToken 合约所拥有的标的资产余额。人民可以查询到目前这个市场上的现金总量。

CErc20 / CEther

1 function getCash() returns (uint)

  • 返回值 : 该 cToken 合约所拥有的标的资产数量。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint cash = cToken.getCash();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const cash = (await cToken.methods.getCash().call());

总借款(Total Borrow)

总借贷额是指目前市场上借出的标的额,以及累计向市场上的供应商支付的利息。

CErc20 / CEther

1 function totalBorrowsCurrent() returns (uint)
  • 返回值:借出的标的总额与利息。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint borrows = cToken.totalBorrowsCurrent();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const borrows = (await cToken.methods.totalBorrowsCurrent().call());

借款余额(Borrow Balance)

从协议中借入资产的用户要根据借款利率进行累计利息。利息是每块都要累计的,集成商可以通过这个功能来获得用户的借款余额的当前价值与利息。

CErc20 / CEther

1 function borrowBalanceCurrent(address account) returns (uint)
  • account : 借入资产的账户。
  • 返回值 : 用户的当前借款余额(与利息),单位是标的资产的单位。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint borrows = cToken.borrowBalanceCurrent(msg.caller);

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const borrows = await cToken.methods.borrowBalanceCurrent(account).call();

借款利率(Borrow Rate)

在任何时候,任何人都可以通过查询合约来获取当前每个区块的借款利率。

CErc20 / CEther

1 function borrowRatePerBlock() returns (uint)
  • 返回值 : 当前借款利率,为一个无符号整数,按 1e18 缩放。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint borrowRateMantissa = cToken.borrowRatePerBlock();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const borrowRate = (await cToken.methods.borrowRatePerBlock().call()) / 1e18;

总供应量(Total Supply)

总供应量是指目前在 cToken 市场上流通的代币数量。它是 cToken 合约的 EIP-20 接口的一部分 。

CErc20 / CEther

1 function totalSupply() returns (uint)
  • 返回值 : 在市场上流通的代币总数量。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2  uint tokens = cToken.totalSupply();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const tokens = (await cToken.methods.totalSupply().call());

标的余额 Balance

用户的标的月,代币他们在协议中的资产,等于用户的 cToken 余额乘以汇率

CErc20 / CEther

1 function balanceOfUnderlying(address account) returns (uint)
  • account : 获取标的余额的账户。
  • 返回值 : 该账户当前拥有的标的数量。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint tokens = cToken.balanceOfUnderlying(msg.caller);

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const tokens = await cToken.methods.balanceOfUnderlying(account).call();

供给率(Supply Rate)

在任何时候,人们都可以通过查询合约来获取当前每块的供给率。供给率是由借款利率、准备金系数(reserve factor)和总借款额得出的。

CErc20 / CEther

1 function supplyRatePerBlock() returns (uint)
  • 返回值 : 当前供给率,为一个无符号整数,按 1e18 缩放。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint supplyRateMantissa = cToken.supplyRatePerBlock();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const supplyRate = (await cToken.methods.supplyRatePerBlock().call()) / 1e18;

总储备金(Total Reserves)

储备金是每个 cToken 合约中的会计分录,代币历史利息的一部分,作为现金预留,可以通过协议的治理来提取或转移。借款人利息的一小部分应计入协议中,由准备金系数 reserve factor决定。

CErc20 / CEther

1 function totalReserves() returns (uint)
  • 返回值 : 市场上持有的储备金总额。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2 uint reserves = cToken.totalReserves();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...);
2 const reserves = (await cToken.methods.totalReserves().call());

储备金系数(Reserve Factor)

准备金系数确定了借款人利息中转化为准备金的部分。

CErc20 / CEther

1 function reserveFactorMantissa() returns (uint)
  • 返回值 : 当前市场准备金系数,为一个无符号整数,按 1e18 缩放。

Solidity

1 CErc20 cToken = CToken(0x3FDA...);
2uint reserveFactorMantissa = cToken.reserveFactorMantissa();

Web3 1.0

1 const cToken = CEther.at(0x3FDB...); 
2 const reserveFactor = (await cToken.methods.reserveFactorMantissa().call()) / 1e18;
该文章收录于
Compound 研究
149 订阅 5 篇内容

0 条评论