20230325cakeifo代码实现示例及我的理解及注释

// SPDX-License-Identifier: MIT

pragma solidity ^0.6.12;

import "@openzeppelin/contracts/access/Ownable.sol";

import "@openzeppelin/contracts/math/SafeMath.sol";

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";

import "profile-nft-gamification/contracts/PancakeProfile.sol";

import "./interfaces/IIFOV2.sol";

import "pancake-cake-vault/contracts/IFOPool.sol";

import "pancake-cake-vault/contracts/test/CakeToken.sol";

import "pancake-cake-vault/contracts/test/SyrupBar.sol";

import "pancake-cake-vault/contracts/test/MasterChef.sol";

/**

* @title my cake ifo v3 implement

* @author lrqstudy

* @notice

*/

contract MyIFOInitializableV3 is IIFOV2,ReentrancyGuard,Ownerable {

    /**

    * 使用了openzeppelin中的safemath,将其方法用于uint256中,这个是之前的版本,现在的新的solidity版本不需要

    */

    using SafeMath for uint256;

    using SafeERC20 for IERC20;

    // 定义了一个池子数的常量,公开的和私人的

    uint8 public constant NUMBER_POOLS = 2;

    // ifo工厂的地址

    address public immutable IFO_FACTORY;



    uint256 public MAX_BUFFER_BLOCKS;

    //用于参与本次ifo 的资金

    IERC20 public lpToken;

    //用于公开售卖的token地址

    IERC20 public offeringToken;

    //pancake平台信息,只有注册信息才可以参与ifo

    PancakeProfile public pancakeProfile;

    IFOPool public ifoPool;

    bool public isInitialized;

    //ifo 开始区块号

    uint256 public startBlock;

    //ifo 结束区块号

    uint256 public endBlock;

    //本次ifo的活动id

    uint256 public campaignId;

    //参与达到要求后有多少积分

    uint256 public numberPoints;

    //最低积分要求

    uint256 public thresholdPoints;

    //总共要售卖的token数量

    uint256 public totalTokensOffered;

    PoolCharacteristics[NUMBER_POOLS] private _poolInformation;

    //定义了一个mapping结构,key是地址,value是状态,描述用户是否已经领取积分

    mapping(address => bool) private _hasClaimedPoints;

    /**

    * @dev 定了一个潜逃的mapping结构, 第一层key是用户地址,value是一个 key为poolid, value为userinfo的结构mapping结构,

    用于记录当前用户参与ifo的信息,参与了几个池子。

    */

    mapping(address => mapping(uint8 => UserInfo)) private _userInfo;

    //定义了一个mapping,key是用户参与地址, value是用户累计参与的金额。

    mapping(address => uint256) public userAccumulateDeposits;

    /**

    * 定义了一个池子的结构

    * 包括了六个对象, 募集目标金额,共计提供多少token,每个用户多少险恶,是否有参与费用,总共的募集资金,总共产生多少费用

    * @param amountLP

    * @param amountOfferingToken

    */

    struct PoolCharacteristics {

        uint256 raisingAmountPool;

        uint256 offeringAmountPool;

        uint256 limitPerUserInLP;

        bool hasTax;

        uint256 totalAmountPool;

        uint256 sumTaxesOverflow;

    }

    /**

    * 定义了一个userinfo的结构,存储用户参与了多少资金,是否领取的状态

    * @param amountLP

    * @param amountOfferingToken

    */

    struct UserInfo {

        uint256 amountPool;

        bool claimedPool;

    }

    event AdminWithdraw(uint256 amountLP, uint256 amountOfferingToken);

    event AdminTokenRecovery(address tokenAddress, uint256 amountTokens);

    event Deposit(address indexed user, uint256 amount, uint8 indexed pid);

    event Harvest(address indexed user, uint256 offeringAmount,uint256 excessAmount, uint8 indexed pid);

    event NewStartAndEndBlocks(uint256 startBlock,uint256 endBlock);

    event PointParametersSet(uint256 campaignId, uint256 numberPoints, uint256 thresholdPoints);

    event PoolParametersSet(uint256 offeringAmountPool, uint256 raisingAmountPool, uint8 pid);

    /**

    * 定义了一个修饰符,合约以及代理合约对象不能参与,只有普通的eta地址可以参与

    */

    modifier notContract() {

        require(!_isContract(msg.sender)," contract not allowed");

        require(msg.sender == tx.origin," proxy contract not allowed");

        _;

    }

    /**

    * 定义了一个构造函数,指定IFO_Factory对象地址

    */

    constructor() public {

        IFO_FACTORY = msg.sender;

    }

    /**

    * 定义了一个初始化函数,用于初始化设置当前ifo事件

    * @param _lpToken  用于参与的资金token地址

    * @param _offeringToken  用于售卖的token地址

    * @param _pancakeProfileAddress pancakefile的合约地址

    * @param _startBlock 售卖开始的区块编号

    * @param _endBlock  售卖结束的区块编号

    * @param _maxBufferBlocks 最大缓冲区块数,用于控制ifo的冗余时间

    * @param _adminAddress  管理员地址,用于提取当前资金

    * @param _ifoPoolAddress  ifo池子地址,用于接收

    */

    function initialize(

        address _lpToken,

        address _offeringToken,

        address _pancakeProfileAddress,

        uint256 _startBlock,

        uint256 _endBlock,

        uint256 _maxBufferBlocks,

        address _adminAddress,

        address _ifoPoolAddress

    )  public  {

        require(!isInitialized,"operations: Already initialized");

        require(msg.sender == IFO_FACTORY, "Operation: only for Factory");

        //make this contract initialized

        isInitialized = true;

        lpToken = IERC20(_lpToken);

        offeringToken = IERC20(_offeringToken);

        /**

        * 这里无法理解,为什么 PancakeProfile创建了对象,怎么就一个参数就可以呢?

        * PancakeProfile的构造函数有四个参数。

        * @param _amount

        * @param _pid

        */

        pancakeProfile = PancakeProfile(_pancakeProfileAddress);


        ifoPool = IFOPool(_ifoPoolAddress);

        startBlock = _startBlock;

        endBlock = _endBlock;

        MAX_BUFFER_BLOCKS = _maxBufferBlocks;

        transferOwnership(_adminAddress);//这个将当前合约转移给管理员

    }

    /**

    * 用于处理用户参与的请求函数

    * @param _amount 本次参与金额,

    * @param _pid 本次参与的池子编号

    */

    function depositPool(uint256 _amount,uint8 _pid) external override nonReentrant notContract {

        //用户必须要注册并获得符合规定的pancake账户及头像,从而参与ifo

        require(pancakeProfile.getUserStatus(msg.sender),"Deposit: Must have an active profile");

        //当前只有两个池子,如果池子id不小于2,说明参数有问题

        require(_pid < NUMBER_POOLS," Deposit: Non valid pool id");

        //必须是要先设定好融资额度,以及提供的token数量,用户才能参与进来。

        require(_poolInformation[_pid].offeringAmount > 0 && _poolInformation[_pid].raisingAmountPool>0,"Deposit: Pool not set");

        //只有到了开始的区块号才能参与,用户不能参与

        require(block.number > startBlock,"Deposit: Too early");

        //过了结束参与的区块号,不能参与

        require(block.number< endBlock,"Deposit: Too Late");

        //如果当前ifo合约中的对应的售卖余额,大于已经提供的,那么说明设置的ifo 数据有问题。

        require(offeringToken.balanceOf(address(this))>= totalTokensOffered,"Deposit: Tokens not deposited properly");

        //用户可以参与的额度,用于存入的总金额不得大于该额度

        uint256 ifoCredit = ifoPool.getUserCredit(msg.sender);

        require(userAccumulateDeposits[msg.sender].add(_amount)<= ifoCredit,"Not enough IFO credit left");

        //如果这些校验都通过了,那么将用于提供的cake数量调用转账函数转到当前合约地址中

        lpToken.safeTransferFrom(address(msg.sender),address(this),_amount);

        //将用户成功参与到金额以及池子信息设置到mapping中,方便后续查看

        _userInfo[msg.sender][_pid].amountPool = _userInfo[msg.sender][_pid].amountPool.add(_amount);

        //如果每个用户有参与限额,那么用户的参与金额不得大于这个限额

        if (_poolInformation[_pid].limitPerUserInLP > 0) {

            require(_userInfo[msg.sender][_pid].amountPool <= _poolInformation[_pid].limitPerUserInLPimi,"Deposit: new amount over user limt");

        }

        //将用户本次参与的金额更新到总的参与池子信息中,方便后期查看

        _poolInformation[_pid].totalAmountPool = _poolInformation[_pid].totalAmountPool.add(_amount);

        //记录用于累计参与到ifo金额数量。

        userAccumulateDeposits[msg.sender] = userAccumulateDeposits[msg.sender].add(_amount);

        //发出用户参与ifo的事件。报告参与人,参与金额,参与项目id

        emit Deposit(msg.sender,_amount, _pid);

    }

    /**

    * 定义了用户在参与结束后领取ifo的处理请求函数。 ifo结束后,用户点击claim由当前函数进行处理。

    * @param _pid 项目id

    */

    function harvestPool(uint8 _pid) external override nonReentrant notContract{

        //必须结束ifo才能领取

        require(block.number > endBlock,"Harvest: Too early");

        //项目id必须是0 或者1

        require(_pid < NUMBER_POOLS,"Harvest:Non valid pool id");

        //只有参与过的人才可以领取

        require(_userInfo[msg.sender][_pid].amountPool>0,"Harvest: Did not participate ");

        //如果已经领取了,则不能再次领取

        require(!_userInfo[msg.sender][_pid].claimedPool,"Harvest: already done");

        //调用领取参与积分的函数

        _claimPoints(msg.sender);

        //将用户对应的项目id的已经领取状态设置为true

        _userInfo[msg.sender][_pid].claimedPool = true;

        //计算用户已经参与的金额,需要退还的金额,以及用户参与的费用

        (

            uint256 offeringTokenAmount,

            uint256 refundingTokenAmount,

            uint256 userTaxOverflow

        ) = _calculateOfferingAndRefundingAmountPool(msg.sender,_pid);

        if(userTaxOverflow > 0){

            _poolInformation[_pid].sumtaxedOverflow = _poolInformation[_pid].sumTaxesOverflow.add(userTaxOverflow);

        }

        //将用户本次获得的token数转给当前用户

        if(offeringTokenAmount > 0) {

            //疑问: 这个是如何设置用户的领取周期的 TODO

            offeringToken.safeTransfer(address(msg.sender),offeringTokenAmount);

        }

        //将用户超额部分多cake退还给用户

        if(refundingTokenAmount>0){

            lpToken.safeTransfer(address(msg.sender),refundingTokenAmount);

        }

        //调用通知事件,告知领取人,提供的金额,以及退换的金额。

        emit Harvest(msg.sender, offeringTokenAmount,refundingTokenAmount,_pid);

    }

    /**

    * 供ifo项目方领取募集资金以及剩余的出售token,仅仅当初设置为合约所有人地址才能调用

    * @param _lpAmount 领取募集资金额度

    * @param _offerAmount 领取剩余出售token数量

    */

    function finalWithdraw(uint256 _lpAmount,uint256 _offerAmount) external override onlyOwner {

        //领取数必须小于等于当前合约中剩余的cake数

        require(_lpAmount <= lpToken.balanceOf(address(this)),"Operations: Not enough LP tokens");

        //领取的token数必须小于等于当前合约剩余的token数

        require(_offerAmount <= offeringToken.balanceOf(this),"Operations Not enough offering tokens");

        if(_lpAmount > 0){

            //当前合约中的cake转给项目方

            lpToken.safeTransfer(address(msg.sender),_lpAmount);

        }

        if(_offerAmount >0){

            //将当前合约中剩余的未出售token转给项目方

            offeringToken.safeTransfer(address(msg.sender),_offerAmount);

        }

        //发送管理员领取事件

        emit AdminWithdraw(_lpAmount,_offerAmount);

    }

    /**

    * 给合约所有者恢复错误转入的token,且必须是除了cake和当前待出售的合约。

    * @param _tokenAddress

    * @param _tokenAmount

    */

    function recoverWrongTokens(address _tokenAddress,uint256 _tokenAmount) external onlyOwner {

        //要转移的token不能是cake

        require(_tokenAddress != address(lpToken), "can not be LP token");

        //要恢复的token也不能是项目方的出售token

        require(_tokenAddress != address(offeringToken),"recover: cannot be offering token");

        //将当前合约对象强制转换为ERC20对象,从而能调用erc20对象的safeTransfer方法,将当前合约中的当前token转给项目方

        IERC20(_tokenAddress).safeTransfer(address(msg.sender),_tokenAmount);

        //发布管理员恢复误操作token

        emit AdminTokenRecovery(_tokenAddress,_tokenAmount);

    }

    /**

    * 定义了一个设置启动参数的功能,给管理员调用

    * @param _offeringAmountPool 待出售token数量

    * @param _raisingAmountPool  共计募集资金

    * @param _limitPerUserInLP  每个参与人参与限额

    * @param _hasTax  是否有费用

    * @param _pid  池子id

    */

    function setPool(

        uint256 _offeringAmountPool,

        uint256 _raisingAmountPool,

        uint256 _limitPerUserInLP,

        bool _hasTax,

        uint8 _pid

        ) external override onlyOwner {

            //只有开始的区块之前才可以调用,在开始后不允许调用。

            require(block.number < startBlock," Operations: IFO has started");

            //池子id必须是指定的几个。

            require(_pid < NUMBER_POOLS," Operations: Pool does not exist");

            //将传递的参数赋值给poolInformation状态变量

            _poolInformation[_pid].offeringAmountPool = _offeringAmountPool;

            _poolInformation[_pid].raisingAmountPool = _raisingAmountPool;

            _poolInformation[_pid].limitPerUserInLP = _limitPerUserInLP;

            _poolInformation[_pid].hasTax = _hasTax;

            uint256 tokensDistributedAcrossPools;

            for(uint8 i =0; i< NUMBER_POOLS;i++){

                // 统计两个池子共提供的token数

                tokensDistributedAcrossPools = tokensDistributedAcrossPools.add(_poolInformation[i].offeringAmountPool);

            }

            totalTokensOffered = tokensDistributedAcrossPools;

            //发送池子参数设定的事件

            emit PoolParametersSet(_offeringAmountPool,_raisingAmountPool,_pid);

    }

    /**

    * 更新积分参数,只能在区块结束之前调用

    * @param _campaignId ifo活动id

    * @param _numberPoints 积分数

    * @param _thresholdPoints 积分门槛

    */

    function updatePointParameters(

        uint256 _campaignId,

        uint256 _numberPoints,

        uint256 _thresholdPoints

        ) external override onlyOwner {

            require(block.number < endBlock,"operation IFO has ended");

            numberPoints = _numberPoints;

            campaignId = _campaignId;

            thresholdPoints = _thresholdPoints;

            // 发布积分更新时间

            emit PointParametersSet(campaignId,numberPoints,thresholdPoints);

    }

    /**

    * 更新开始结束区块号,从而重新设定开始结束时间。

    * @param _startBlock 新的开始区块

    * @param _endBlock  新的结束区块

    */

    function updateStartAndEndBlocks(uint256 _startBlock,uint256 _endBlock) external onlyOwner {

        //新的结束区块只能在当前区块延后 的MAX_BUFFER_BLOCKS 中

        require(_endBlock < (block.number + MAX_BUFFER_BLOCKS), "Operations: EndBlock too far");

        //当前操作必须要在开始之前操作

        require(block.number < startBlock,"Operations: IFO has started");

        //新设置的结束区块必须要大于开始区块

        require(_startBlock < _endBlock, "Operations: New startBlock must be lower than new endBlock");

        //新设置的区块必须要在当前系统区块之后。

        require(block.number < _startBlock,"Operations: New startblock must be higher than current block");

        startBlock = _startBlock;

        endBlock = _endBlock;

        //发布新开始结束时间区块事件

        emit NewStartAndEndBlocks(_startBlock,_endBlock);

    }

    /**

    * 定义一个访问ifo池子的方法。

    * @param _pid

    * @return

    * @return

    * @return

    * @return

    * @return

    * @return

    */

    function viewPoolInformation(uint256 _pid) external view override returns (

        uint256,

        uint256,

        uint256,

        bool,

        uint256,

        uint256

        ){

            return (

                _poolInformation[_pid].raisingAmountPool,

                _poolInformation[_pid].offeringAmountPool,

                _poolInformation[_pid].limitPerUserInLP,

                _poolInformation[_pid].hasTax,

                _poolInformation[_pid].totalAmountPool,

                _poolInformation[_pid].sumTaxesOverflow

            );

    }


    /**

    * 定义一个通过池子id获得当前池子溢出税,如果没有税,则返回0,如果有税,则返回计算税的结果

    * @param _pid

    */

    function viewPoolTaxRateOverflow(uint256 _pid) external view override returns (uint256){

        if(!_poolInformation[_pid].hasTax){

            return 0;

        }

        return _calculateTaxOverflow(_poolInformation[_pid].totalAmountPool,_poolInformation[_pid].raisingAmountPool);

    }

    /**

    *

    * @param _user

    * @param _pids

    */

    function viewUserAllocationPools(address _user,uint8[] calldata _pids) external view override returns (uint256[] memory){

        uint256[] memory allocationPools = new uint256[](_pids.length);

        for(uint8 i=0; i< _pids.length;i++){

            allocationPools[i] = _getUserAllocationPool(_user,_pids[i]);

        }

        return allocationPools;

    }

    /**

    * 定义了一个访问用户数据的方法。 返回值有两个数组。

    * 第一个是用户参与每个池子对应的金额数,第二个数组是每个用户是否领取了对应的token

    * @param _user

    * @param _pids

    * @return

    * @return

    */

    function viewUserInfo(address _user, uint8[] calldata _pids) external view override returns(uint256[] memory, bool[] memory){

        uint256[] memory amountPools = new uint256[](_pids.length);

        bool[] memory statusPools = new bool[](_pids.length);

        for(uint8 i = 0; i< NUMBER_POOLS; i++){

            amountPools[i] = _userInfo[_user][i].amountPool;

            statusPools[i] = _userInfo[_user][i].claimedPool;

        }

        return (amountPools,statusPools);

    }

    /**

    * 获取并返回用户在每个池子中参与金额以及退款的金额数

    */

    function viewUserOfferingAndRefundingAmountsForPools(address _user,uint8[] calldata _pids) external

    view override returns (uint256[3][]memory){

        uint256[3][] memory amountPools = new uint256[3][](_pids.length);

        for(uint8 i =0; i<_pids.length;i++){

            uint256 userOfferingAmountPool;

            uint256 userRefundingAmountPool;

            uint256 userTaxAmountPool;

            if(_poolInformation[_pids[i]].raisingAmountPool > 0){

                (

                    userOfferingAmount,

                    userRefundingAmountPool,

                    userTaxAmountPool

                ) = _calculateOfferingAndRefundingAmountsPool(_user,_pids[i]);

            }

            amountPools[i] = [userOfferingAmountPool,userRefundingAmountPool,userTaxAmountPool];

        }

        return amountPools;

    }

    /**

    * @notice It calculates the tax overflow given the raisingAmountPool and the totalAmountPool.

    * @dev 100,000,000,000 means 0.1 (10%) / 1 means 0.0000000000001 (0.0000001%) / 1,000,000,000,000 means 1 (100%)

    * @return It returns the tax percentage

    */

    function _calculateTaxOverflow(uint256 _totalAmountPool, uint256 _raisingAmountPool)

        internal

        pure

        returns (uint256)

    {

        uint256 ratioOverflow = _totalAmountPool.div(_raisingAmountPool);

        if (ratioOverflow >= 1500) {

            return 500000000; // 0.05%

        } else if (ratioOverflow >= 1000) {

            return 1000000000; // 0.1%

        } else if (ratioOverflow >= 500) {

            return 2000000000; // 0.2%

        } else if (ratioOverflow >= 250) {

            return 2500000000; // 0.25%

        } else if (ratioOverflow >= 100) {

            return 3000000000; // 0.3%

        } else if (ratioOverflow >= 50) {

            return 5000000000; // 0.5%

        } else {

            return 10000000000; // 1%

        }

    }

    /**

    * @notice It calculates the offering amount for a user and the number of LP tokens to transfer back.

    * @param _user: user address

    * @param _pid: pool id

    * @return {uint256, uint256, uint256} It returns the offering amount, the refunding amount (in LP tokens),

    * and the tax (if any, else 0)

    */

    function _calculateOfferingAndRefundingAmountsPool(address _user, uint8 _pid)

        internal

        view

        returns (

            uint256,

            uint256,

            uint256

        )

    {

        uint256 userOfferingAmount;

        uint256 userRefundingAmount;

        uint256 taxAmount;

        if (_poolInformation[_pid].totalAmountPool > _poolInformation[_pid].raisingAmountPool) {

            // Calculate allocation for the user

            uint256 allocation = _getUserAllocationPool(_user, _pid);

            // Calculate the offering amount for the user based on the offeringAmount for the pool

            userOfferingAmount = _poolInformation[_pid].offeringAmountPool.mul(allocation).div(1e12);

            // Calculate the payAmount

            uint256 payAmount = _poolInformation[_pid].raisingAmountPool.mul(allocation).div(1e12);

            // Calculate the pre-tax refunding amount

            userRefundingAmount = _userInfo[_user][_pid].amountPool.sub(payAmount);

            // Retrieve the tax rate

            if (_poolInformation[_pid].hasTax) {

                uint256 taxOverflow = _calculateTaxOverflow(

                    _poolInformation[_pid].totalAmountPool,

                    _poolInformation[_pid].raisingAmountPool

                );

                // Calculate the final taxAmount

                taxAmount = userRefundingAmount.mul(taxOverflow).div(1e12);

                // Adjust the refunding amount

                userRefundingAmount = userRefundingAmount.sub(taxAmount);

            }

        } else {

            userRefundingAmount = 0;

            taxAmount = 0;

            // _userInfo[_user] / (raisingAmount / offeringAmount)

            userOfferingAmount = _userInfo[_user][_pid].amountPool.mul(_poolInformation[_pid].offeringAmountPool).div(

                _poolInformation[_pid].raisingAmountPool

            );

        }

        return (userOfferingAmount, userRefundingAmount, taxAmount);

    }

    /**

    * 定义了一个供内部调用的领取积分函数

    * @param _user

    */

    function _claimPoints(address _user) internal  {

        if(_hasClaimedPoints[_user]){

            return;//如果当前用户已经领取积分,那么不执行后续命令? 疑问,为什么这里不报错呢,而仅仅是返回。

        }

        uint256 sumPools;

        for(uint8 i=0; i<NUMBER_POOLS;i++){

            sumPools = sumPools.add(_userInfo[msg.sender][i].amountPool);//统计用户参与的总的金额数

        }

        //如果用户参与的总金额数,超过了限额,则满足领取积分条件

        if(sumPools > thresholdPoints) {

            _hasClaimedPoints = true;//将已经领取积分的状态设置为true

            pancakeProfile.increaseUserPoints(msg.sender, numberPoints,campaignId);//调用pancakeProfile合约执行给用户增加积分的函数功能。

        }


    }

    /**

    * 获取用户参与量占当前总量的比例

    * @param _user

    * @param _pid

    */

    function _getUserAllocationPool(address _user,uint8 _pid) internal view returns(uint256) {

        if(_poolInformation[_pid].totalAmountPool > 0) {

            return _userInfo[_user][_pid].amountPool.mul(1e18).div(_poolInformation[_pid]).totalAmountPool.mul(1e6);

        }

        return 0;

    }

    /**

    * 定义一个判断是否是合约地址的内部方法

    * @param _addr

    */

    function _isContract(address _addr) internal  view returns (bool){

        uint256 size;

        assembly {

            size := extcodesize(_addr)

        }

        return size >0;

    }

}

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

推荐阅读更多精彩内容