Defi部署教程 Compound部署 搭建去中心化借贷银行

本文环境
操作系统:maxOS 10.15.6
科学上网
公链网络:BSC Testnet
测试工具:Remix IDE、MetaMask
合约源码:https://github.com/compound-finance/compound-protocol.git

在 remix 进行编译部署时,勾选启用优化。

Compound中含有的合约代码量很大,文件数量30+,一开始笔者也忍不住打了退堂鼓。然而学习就是一个从难到易的过程,只有花时间去努力学习,才能慢慢理解它的结构和细节,最终会赞美Compound团队提供的优秀代码,优秀方案!
下图是网上找的Compound合约结构图,以飨读者。在部署前,先梳理清楚各个合约之间的关系,并将其分组,梳理出各个模块,及初始化参数。

Compound 合约结构

一、COMP 模块

激励资产合约,可以使用标准 ERC20 合约。
因为 Comptroller 中会使用到 COMP 的地址,因此我们最先部署 COMP 合约,得到合约地址: 0x1fe7FF222D59B6648D640090046661313A1CF0a2
部署完成后,到合约 ComptrollerG7.sol (或者计划使用的 Comptroller 合约)进行配置,更改为自己的 COMP 合约地址。
也可以直接使用Comp.sol部署 compound自带的COMP token合约,修改一下name,symbol,totalSupply等。

     /**
     * @notice Return the address of the COMP token
     * @return The address of COMP
     */
    function getCompAddress() public view returns (address) {
        return 0x1fe7FF222D59B6648D640090046661313A1CF0a2;
    }

二、comptroller 模块

在compound设计中,unitroller 是代理合约,comptroller 是逻辑实现合约,通过 delegatecall 来实现远程合约调用。

2.1 部署 Unitroller.sol

使用 account1 账号进行部署,成功:

contract address: 0x268e3eF4380DA46e55B77a6263810910722a875E

2.2 部署 ComptrollerG7.sol

使用 account1 账号进行部署;成功:

contract address: 0x67006E2110119Abfd40b2A743A85b4d3bF8967b9

三、priceOracle 模块

3.1 部署 SimplePriceOracle.sol

使用 account1 账号进行部署

contract address: 0x5991199a9aB1801A229a2E49a042471eDE997a21

四、绑定与设置

4.1 代理绑定

  • 第一步: 在 Unitroller.sol 合约调用 _setPendingImplementation;
    参数 address newPendingImplementation,这里设置为 ComptrollerG7.sol 地址

  • 第二步: 在 ComptrollerG7.sol 合约调用 _become,
    参数 Unitroller unitroller,这里设置为 Unitroller.sol 地址

代理绑定,第一步转移所有权,第二步新的 Comptroller 接受所有权,这样就可以防止意外地升级到无效的合约;
备注:设置完成后对外提供 Comptroller 合约地址时, 提供的是 Unitroller 合约地址。

以下步骤,请 unitrollerProxy = ComptrollerG7(address(unitroller));
at Address unitrollerAddr得到unitrollerProxy合约,名字还是ComptrollerG7

4.2 设置 closeFactor

在 ComptrollerG7.sol 合约调用 _setCloseFactor,
参数 uint newCloseFactorMantissa,这里设置为 50%,即:0.5 * 1 ^18 = 500000000000000000

4.3 设置 liquidationIncentiveMantissa

在 ComptrollerG7.sol 合约调用 _setLiquidationIncentive,
参数 uint newLiquidationIncentiveMantissa,设置流动性激励为 8%,参数值就是1.08 * 1 ^ 18 = 1080000000000000000

4.4 设置 oracle

在 ComptrollerG7.sol 合约调用 _setPriceOracle,
参数 PriceOracle newOracle,这里设置为 SimplePriceOracle.sol 地址:0x5991199a9aB1801A229a2E49a042471eDE997a21

五、interestRate 模块

拐点利率模型

5.1 部署 JumpRateModelV2.sol

部署时参数:

  • uint baseRatePerYear, 实际设置为 0
  • uint multiplierPerYear, 实际设置 7%, 即 0.07 * 10 ^ 18 = 70000000000000000
  • uint jumpMultiplierPerYear, 实际设置 3, 即 3 * 10 ^ 18 = 3000000000000000000
  • uint kink_, 实际设置 75%, 即 0.75 * 10 ^ 18 = 750000000000000000
  • address owner_, 实际设置 msg.sender

使用 account1 账号进行部署,成功:

contract address: 0x8A517DA790929D2aC3527210f9472E2822424180

备注: 部署后, 参数都可以用 updateJumpRateModel 方法进行修改;

5.2 部署另一个 JumpRateModelV2.sol

如果是测试,只需要部署一个就可以了,使用erc20的,
因为 cToken 跟 JumpRateModelV2 需要一一对应的关系,因此再次部署该合约,用于后面分别与 CErc20Delegator.sol 和 CEther.sol 对应.
部署时参数跟5.1节相同;
使用 account1 账号进行部署,成功:

contract address: 0x0cca4ccD1ED542B5D7F3Ebbcf49D92DCB0a8D04e

六、CToken 模块(ERC20)

6.1 部署 ERC20Token.sol

部署一个标准 ERC20 代币,作为基础资产用于测试,
例子:使用 account1 账号进行部署usdt合约,成功:

contract address:  0xBEA207ec294BCe7a866C3a598195A61Bb7E8D599

6.2 部署 CErc20Delegate.sol

此合约给支持代理的 cToken 合约使用,不支持代理的 cToken 不需要使用这个合约;
所有 ERC20 基础资产的 CToken 采用委托代理模式,所以我们先部署一个实现合约:
使用 account1 账号进行部署,成功:

contract address: 0xc176eD65274b2a2d422126d597Be715fc97d2e98

6.3 部署 CErc20Delegator.sol

此合约即为与代币类型(ERC20)的标的资产对应的 cToken 合约;
部署时参数:

  • address underlying_, erc20标的资产地址,见6.1节
  • ComptrollerInterface comptroller_, ComptrollerG7.sol 合约地址,见2.2节
  • InterestRateModel interestRateModel_, JumpRateModelV2合约地址,见5.1节
  • uint initialExchangeRateMantissa_, 初始汇率,按 1:1 设置,比列见备注说明,本文 1 * 10 ^ 18 = 100000000000000000
  • string memory name_, cToken 的 name COMPOUND USD
  • string memory symbol_, cToken 的 symbol cUSD
  • uint8 decimals_, cToken 的 decimals, 设为 18
  • address payable admin_, 应该是时间锁定合约地址,此处设为 msg.sender
  • address implementation_, CErc20Delegate 合约地址,见6.2节
  • bytes memory becomeImplementationData, 额外初始数据,此处填入0x;即无数据

备注:initialExchangeRateMantissa_ = 1 * 10 ^ (18 + underlyingDecimals - cTokenDecimals)

使用 account1 账号进行部署,成功:

contract address: 0x209C9b6a0Ec37b91d0758514070A8439B14B9B3c

七、CToken 模块(ETH)

7.1 部署 CEther.sol

此合约即为与主币类型(ETH)对应的 cToken 合约,
部署时参数:

  • ComptrollerInterface comptroller_, unitroller合约地址,见2.1节
  • InterestRateModel interestRateModel_, JumpRateModelV2合约地址,见5.2节
  • uint initialExchangeRateMantissa_, 初始汇率,按 1:1 设置,本文 1 * 10 ^ 18 = 100000000000000000
  • string memory name_, cToken 的 name COMPOUND ETHER
  • string memory symbol_, cToken 的 symbol cETH
  • uint8 decimals_, cToken 的 decimals,设为 18
  • address payable admin_, 设为 msg.sender

使用 account1 账号进行部署,成功:

contract address: 0xf3feeab27E8B8b71ED92040be19E5aA80baf9B01

八、设置市场价格

在SimplePriceOracle.sol合约里调用setUnderlyingPrice:

8.1设置cUSD的价格:

CToken cToken, CErc20Delegator.sol 地址
uint underlyingPriceMantissa, 1 * 10 ^ 18 = 1000000000000000000
使用 account1 账号进行cUSD价格设置操作,成功

8.2设置cETH的价格:

CToken cToken, CEther.sol 地址
uint underlyingPriceMantissa, 2000 * 10 ^ 18 = 2000000000000000000000
使用 account1 账号进行cETH价格设置操作,成功

九、CToken 配置

9.1 设置 ReserveFactor

设置保证金系数

9.1.1 在 CErc20Delegator.sol 调用合约方法 _setReserveFactor:

设置时参数:

  • uint newReserveFactorMantissa , 新的保证金系数, 本文 0.1 * 10 ^ 18 = 100000000000000000

9.1.2 在 CEther.sol 调用合约方法 _setReserveFactor:

设置时参数:

  • uint newReserveFactorMantissa , 新的保证金系数, 本文 0.2 * 10 ^ 18 = 200000000000000000

9.2 CToken 加入市场

在 ComptrollerG7.sol 调用合约方法 _supportMarket:
设置时参数:

  • CToken cToken, CErc20Delegator.sol 或 CEther.sol 地址

本文操作两次,将前面部署的 CErc20Delegator.sol 和 CEther.sol 均加入;
使用 account1 账号进行操作

9.3 设置 CollateralFactor

设置抵押率;
在 ComptrollerG7.sol 调用合约方法 _setCollateralFactor:
设置时参数:

  • CToken cToken, CErc20Delegator.sol 地址
  • uint newCollateralFactorMantissa, 抵押率,本文使用 0.6 * 10 ^ 18 = 600000000000000000

使用 account1 账号进行操作,成功

Remix部署完合约以后,如下图:

部署的合约

十、COMP奖励

用户存和借cToken都会有奖励,如果cToken市场设置了compSpeed。
compSpeed: 整数,表示协议将COMP分配给市场供应商或借款人的速率。价值是分配给市场的每个区块的COMP(单位:wei)。 请注意,并非每个市场都向其参与者分发了COMP。可以设置成0。速度表明市场供应商或借款人获得了多少红利,因此将这个数字翻一番,可以显示市场供应商和借款人获得的红利之和。
//代码示例实现了读取每个以太坊区块分配到单个市场的COMP量。

    /**
     * @notice Set COMP speed for a single market
     * @param cToken The market whose COMP speed to update
     * @param compSpeed New COMP speed for market
     */
    function _setCompSpeed(CToken cToken, uint compSpeed) public {
        require(adminOrInitializing(), "only admin can set comp speed");
        setCompSpeedInternal(cToken, compSpeed);
    }

owner才可以设置
参数

  • cToken 相应的市场cToken地址
  • compSpeed 价值是分配给市场的每个区块的COMP(单位:wei)
    这个方法不执行,默认为0,不分发comp

计算compspeed:需要翻倍计算

const cTokenAddress = '0xabc...';
const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);
let compSpeed = await comptroller.methods.compSpeeds(cTokenAddress).call();
compSpeed = compSpeed / 1e18;
// COMP issued to suppliers OR borrowers
const compSpeedPerDay = compSpeed * 4 * 60 * 24;
// COMP issued to suppliers AND borrowers
const compSpeedPerDayTotal = compSpeedPerDay * 2;

十一 提取(Claim COMP)

每个 Compound 用户都会为他们提供给协议或从协议中借用的每个区块累积COMP。用户可以随时调用Comptroller的 claimComp 方法,将累积的COMP转移到他们的地址。
合约方法:

// Claim all the COMP accrued by holder in all markets
function claimComp(address holder) public
// Claim all the COMP accrued by holder in specific markets
function claimComp(address holder, CToken[] memory cTokens) public
// Claim all the COMP accrued by specific holders in specific markets for their supplies and/or borrows
function claimComp(address[] memory holders, CToken[] memory cTokens, bool borrowers, bool suppliers) public

可以使用claimComp()方法提取个人的comp奖励

const comptroller = new web3.eth.Contract(comptrollerAbi, comptrollerAddress);
await comptroller.methods.claimComp("0x1234...").send({ from: sender });

测试模块

前面我们部署了comptroller合约,现在我们需要写一部分测试,看具体的合约逻辑执行。在最小可运行的compound合约中,我们部署了抵押usd,以及compound铸造出来的token:cUSD. 并部署了cUSD实际调用的逻辑cErc20Delegate, 然后cUSD的借贷模型中采用的是JumpRateModelV2,对应的审计合约是comptrollerG7.

下面我们分别就compound中,最核心的用户交互逻辑来编写5个测试,简单验证逻辑可行性。

1、存 mint

用户向compound中存款的逻辑是:用户向compound中存入USD代币, compound根据当前的汇率算出铸造的cUSD代币数量,将对应的cUSD代币转账给用户。

用户函数:enterMarkets

用户的地址中对应用户的所有资产列表,当计算一个用户的所有流动性时。在借贷一种资产前,一个或者多种资产必须被提供给compound以用作抵押。在借贷发生前,任何借贷出的资产必须通过这种方式添加进入compound中。该函数的返回值是一个列表,即该用户的所有资产列表。
在 ComptrollerG7.sol 调用合约方法 enterMarkets:
参数:cTokens: [
"0x209C9b6a0Ec37b91d0758514070A8439B14B9B3c", // cUSD 地址
"0xf3feeab27E8B8b71ED92040be19E5aA80baf9B01" // cETH 地址
]

ComptrollerG7(address(unitroller)).enterMarkets(addrs);
//此时alice调用enterMarkets后,全局变量accountAssets[alice] = cToken[cUni], markets[cWALKER]={true, 60%,{alice:true},false}
//alice 调用cWALKER的mint方法
WALKER.approve(address(cWALKER),uint(-1));
cWALKER.mint(200000000000000000000); //200
// 200000000000000000000/1000000000000000000  //1:1
cWALKER.balanceOf(alice) =  200000000000000000000; //200
cWALKER.totalSupply() = 200000000000000000000
cWALKER.getCash() = 200000000000000000000
cWALKER.supplyRatePerBlock() = 0 //此时没有借款,利用率为0
ComptrollerG7(address(unitroller)).getAccountLiquidity(alice) = 120000000000000000000 = 200000000000000000000 * 0.6 // 120 用户流动性:为UnderlyingToken * 0.6 * price 
mint

2、借 borrow

借币逻辑是:用户在compound中有多种cToken资产,记录在accountAssets中。然后用户向compound借出一定量的usd资产,同时增加用户的负债额度。compound在接受用户的借款请求时,首先会检查cToken有没有上市,再检查用户是否enterMarket,然后根据现在的预言机报价检查用户的账户流动性。

//alice 在compound中存入了200000000000000000000的WALKER代币,获得了200000000000000000000的cWALKER代币
//alice 向compound提出借款50000000000000000000的WALKER代币
cWALKER.borrow(50000000000000000000);//50
cWALKER.totalBorrows() = 50000000000000000000;//50
cWALKER.getCash() == 150000000000000000000 = 200000000000000000000 - 50000000000000000000//150
cWALKER.supplyRatePerBlock = 2219685438
cWALKER.exchangeRateStored() = 1000000000000000000
cWALKER.borrowRatePerBlock() = 11098427194
利用率:utilization = cWALKER.supplyRatePerBlock / cWALKER.borrowRatePerBlock * (1- 0.25) = 
cWALKER.borrowIndex() = 1000000000000000000
cWALKER.accrualBlockNumber() =  18301817
borrow

3、还 repay

repay操作是borrow的逆操作,可以通过repayBorrow偿还自己的贷款,repayBorrowBehalf代为偿还他人贷款,其具体逻辑是用户批准cToken合约使用其underlying token,先调用accuralInterest计算目前利率指数和对全部借贷额计息,然后调用comptroller.repayBorrowAllowed函数检查是否可以偿还,最后调用repayBorrowFresh偿还。

WALKER.approve(cWALKER, 50000000000000000000);
cWALKER.repayBorrow(50000000000000000000);
cWALKER.totalBorrows() = 129296676810100;
cWALKER.getCash() = 200000000000000000000;//200
cWALKER.supplyRatePerBlock = 0
cWALKER.exchangeRateStored() = 1000000517186707240
cWALKER.borrowRatePerBlock() = 28699
利用率:utilization = cWALKER.supplyRatePerBlock / cWALKER.borrowRatePerBlock * (1- 0.25) = 
cWALKER.borrowIndex() = 1000002585933536202
cWALKER.accrualBlockNumber() =  18302050
repay

4、取 redeem

redeem是mint的逆运算,但在实际逻辑中,增加了一个检查账户虚拟流动性的一项。用户可以调用redeem来偿还给定数量的cToken,或者调用redeemUnderlying来偿还某数量的cToken得到给定数量的underlying Token. redeem操作的步骤是用户批准cUSD合约使用用户的cUSD代币,然后调用accuralInterest函数,来计算最新的利率指数Index,并对totalBorrows计息。再然后是调用comptroller.redeemAllowed函数,计算用户的虚拟流动性,看是否用户有足够的流动性来取走token。最后是redeemFresh函数根据要取走的数值,更新accountBorrow中的数值和totalBorrows。

5、清算 liquidity

发生清算的一种典型情况是,用户enterMarkets了两个market,分别是cUni和cUSDT资金池。然后用户在cUni池中,存入Uni获得一定的cUni。用户凭借cUni在cUSDT资金池中借贷出USDT。然而,由于Uni的价格波动,导致Uni/USDT的价格突然下跌,此时用户放置在cUni池中的cUni的总价值小于了借出的USDT的价值,从而触发外部清算者进行清算。

清算过程整体分为两部分:第一部分是repayBorrower部分,代为偿还underlying token,另一部分是seize部分,即将被清算者的cToken及奖励金一起奖励给清算者。由于清算涉及到两种cToken,故在清算的第一步是分别调用两种cToken的accural Interest函数,计算各自最新的利率指数Index,并计算含息债务总额。然后调用comptroller.liquidateBorrowAllowed函数,计算被清算账户的流动性,如果被清算账户的流动性为正,则不允许清算,如果被清算账户的流动性为负,并验算单笔交易的清算量不能超过被清算账户的最大可清算量,则允许清算。具体清算时,要求清算者不能是被清算者自己,然后计算转给被清算者的cToken数量。

在执行转账cToken到清算者之前,需调用comptroller.seizeAllowed函数,作用是验证调用seize函数的msg.sender和address(this)的comptroller保持一致。然后将清算者的账户余额加上seizeTokens,被清算者的余额减去seizeTokens。在完成seize部分后,函数跳转到repayBorrow部分,代为偿还underlying token。
具体清算的概念,可以看清算概述,比较详细简单。

前端调用的方法

CErc20Delegator.sol是给普通erc20用的,CEther.sol是给链的主币用的,基础的都是cToken。
里面的mint,redeem,redeemUnderlying,borrow,repayBorrow,repayBorrowBehalf都类似的。

    /**
     * @notice Sender supplies assets into the market and receives cTokens in exchange
     * @dev Accrues interest whether or not the operation succeeds, unless reverted
     * @param mintAmount The amount of the underlying asset to supply
     * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
     */
    function mint(uint mintAmount) external returns (uint) {
        bytes memory data = delegateToImplementation(abi.encodeWithSignature("mint(uint256)", mintAmount));
        return abi.decode(data, (uint));
    }
    /**
     * @notice Sender redeems cTokens in exchange for the underlying asset
     * @dev Accrues interest whether or not the operation succeeds, unless reverted
     * @param redeemTokens The number of cTokens to redeem into underlying 将被赎回的cToken的数量
     * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
     * redeem 方法将指定数量的 cToken 转换为标的资产,并将其返还给用户。收到的标的数量等于赎回的 cToken 数量乘以当前汇率。
     * 赎回额必须小于用户的账户流动性和市场可用的流动性。
     */
    function redeem(uint redeemTokens) external returns (uint) {
        return redeemInternal(redeemTokens);
    }

    /**
     * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset
     * @dev Accrues interest whether or not the operation succeeds, unless reverted
     * @param redeemAmount The amount of underlying to redeem 将被赎回的标的的资产数量
     * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
     * redeem underlying 方法将 cToken兑换成指定数量的标的资产,并返回给用户。赎回的 cToken的数量等于收到的标的数量除以当前汇率。
     * 赎回额必须小于用户的账户流动性和市场可用的流动性。
     */
    function redeemUnderlying(uint redeemAmount) external returns (uint) {
        return redeemUnderlyingInternal(redeemAmount);
    }

    /**
      * @notice Sender borrows assets from the protocol to their own address
      * @param borrowAmount The amount of the underlying asset to borrow
      * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
      * borrow 方法将协议中的标的资产转移给用户,并创建一个借款余额,根据该资产的借款利率开始累积利息。
      * 借款额必须小于用户的账户流动性和市场可用的流动性。
      */
    function borrow(uint borrowAmount) external returns (uint) {
        return borrowInternal(borrowAmount);
    }

    /**
     * @notice Sender repays their own borrow
     * @dev Reverts upon any failure
     * repay 方法将标的资产转移到协议中,并减少用户的借款余额。
     */
    function repayBorrow() external payable {
        (uint err,) = repayBorrowInternal(msg.value);
        requireNoError(err, "repayBorrow failed");
    }

    /**
     * @notice Sender repays a borrow belonging to borrower
     * @dev Reverts upon any failure
     * @param borrower the account with the debt being payed off
     */
    function repayBorrowBehalf(address borrower) external payable {
        (uint err,) = repayBorrowBehalfInternal(borrower, msg.value);
        requireNoError(err, "repayBorrowBehalf failed");
    }

在 ComptrollerG7.sol 调用合约方法 enterMarkets:

参数:cTokens: [
"0x209C9b6a0Ec37b91d0758514070A8439B14B9B3c", // cUSD 地址
"0xf3feeab27E8B8b71ED92040be19E5aA80baf9B01" // cETH 地址
]

ComptrollerG7(address(unitroller)).enterMarkets(addrs);

参考:

  1. 第108篇 Compound 简单部署
  2. Compound 合约部署
  3. 清算概述
  4. Compound学习
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容