以太坊開發(fā)實戰(zhàn)學(xué)習(xí)-合約安全(八)

通過上一節(jié)的學(xué)習(xí)舶得,我們完成了 ERC721 的實現(xiàn)。并不是很復(fù)雜爽蝴,對吧沐批?很多類似的以太坊概念,當(dāng)你只聽人們談?wù)撍鼈兊臅r候霜瘪,會覺得很復(fù)雜。所以最簡單的理解方式就是你自己來實現(xiàn)它惧磺。

一颖对、預(yù)防溢出

不過要記住那只是最簡單的實現(xiàn)。還有很多的特性我們也許想加入到我們的實現(xiàn)中來磨隘,比如一些額外的檢查缤底,來確保用戶不會不小心把他們的僵尸轉(zhuǎn)移給0 地址(這被稱作 “燒幣”, 基本上就是把代幣轉(zhuǎn)移到一個誰也沒有私鑰的地址顾患,讓這個代幣永遠也無法恢復(fù))。 或者在 DApp 中加入一些基本的拍賣邏輯个唧。(你能想出一些實現(xiàn)的方法么江解?)

但是為了讓我們的課程不至于離題太遠,所以我們只專注于一些基礎(chǔ)實現(xiàn)徙歼。如果你想學(xué)習(xí)一些更深層次的實現(xiàn)犁河,可以在這個教程結(jié)束后,去看看 OpenZeppelin 的 ERC721 合約魄梯。

合約安全增強:溢出和下溢

我們將來學(xué)習(xí)你在編寫智能合約的時候需要注意的一個主要的安全特性:防止溢出和下溢桨螺。

什么是溢出(overflow)?

假設(shè)我們有一個 uint8, 只能存儲8 bit數(shù)據(jù)。這意味著我們能存儲的最大數(shù)字就是二進制 11111111 (或者說十進制的 2^8 - 1 = 255).

來看看下面的代碼酿秸。最后 number 將會是什么值灭翔?

uint8 number = 255;
number++;

在這個例子中,我們導(dǎo)致了溢出 — 雖然我們加了1辣苏, 但是 number 出乎意料地等于 0了肝箱。 (如果你給二進制 111111111, 它將被重置為 00000000,就像鐘表從 23:59 走向 00:00)稀蟋。

下溢(underflow)也類似煌张,如果你從一個等于 0 的 uint8 減去 1, 它將變成 255 (因為 uint 是無符號的,其不能等于負數(shù))糊治。

雖然我們在這里不使用 uint8唱矛,而且每次給一個 uint256 加 1 也不太可能溢出 (2^256 真的是一個很大的數(shù)了),在我們的合約中添加一些保護機制依然是非常有必要的井辜,以防我們的 DApp 以后出現(xiàn)什么異常情況绎谦。

使用 SafeMath

為了防止這些情況,OpenZeppelin 建立了一個叫做 SafeMath 的 庫(library)粥脚,默認情況下可以防止這些問題窃肠。

不過在我們使用之前…… 什么叫做庫?

一個是 Solidity 中一種特殊的合約。其中一個有用的功能是給原始數(shù)據(jù)類型增加一些方法刷允。

比如冤留,使用 SafeMath 庫的時候,我們將使用 using SafeMath for uint256 這樣的語法树灶。 SafeMath 庫有四個方法 — add纤怒, submul天通, 以及 div〔淳剑現(xiàn)在我們可以這樣來讓 uint256 調(diào)用這些方法:

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

我們將在下一章來學(xué)習(xí)這些方法,不過現(xiàn)在我們先將 SafeMath 庫添加進我們的合約。

實戰(zhàn)演練

我們已經(jīng)幫你把 OpenZeppelin 的 SafeMath 庫包含進 safemath.sol了烘豹,如果你想看一下代碼的話瓜贾,現(xiàn)在可以看看,不過我們下一節(jié)將深入進去携悯。

首先我們來告訴我們的合約要使用 SafeMath祭芦。我們將在我們的 ZombieFactory 里調(diào)用,這是我們的基礎(chǔ)合約 — 這樣其他所有繼承出去的子合約都可以使用這個庫了憔鬼。

  • 1龟劲、將 safemath.sol 引入到 zombiefactory.sol.
  • 2、添加定義: using SafeMath for uint256;.

zombiefactory.sol

pragma solidity ^0.4.19;

import "./ownable.sol";
// 1\. 在這里引入
import "./safemath.sol";

contract ZombieFactory is Ownable {

  // 2\. 在這里定義 using safemath 
  using SafeMath for uint 256;
  event NewZombie(uint zombieId, string name, uint dna);

  uint dnaDigits = 16;
  uint dnaModulus = 10 ** dnaDigits;
  uint cooldownTime = 1 days;

  struct Zombie {
    string name;
    uint dna;
    uint32 level;
    uint32 readyTime;
    uint16 winCount;
    uint16 lossCount;
  }

  Zombie[] public zombies;

  mapping (uint => address) public zombieToOwner;
  mapping (address => uint) ownerZombieCount;

  function _createZombie(string _name, uint _dna) internal {
    uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
    zombieToOwner[id] = msg.sender;
    ownerZombieCount[msg.sender]++;
    NewZombie(id, _name, _dna);
  }

  function _generateRandomDna(string _str) private view returns (uint) {
    uint rand = uint(keccak256(_str));
    return rand % dnaModulus;
  }

  function createRandomZombie(string _name) public {
    require(ownerZombieCount[msg.sender] == 0);
    uint randDna = _generateRandomDna(_name);
    randDna = randDna - randDna % 100;
    _createZombie(_name, randDna);
  }

}

二逊彭、SafeMath

來看看 SafeMath 的部分代碼:

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

首先我們有了 library 關(guān)鍵字 — 庫和 合約很相似咸灿,但是又有一些不同。 就我們的目的而言侮叮,庫允許我們使用 using 關(guān)鍵字避矢,它可以自動把庫的所有方法添加給一個數(shù)據(jù)類型:

using SafeMath for uint;
// 這下我們可以為任何 uint 調(diào)用這些方法了
uint test = 2;
test = test.mul(3); // test 等于 6 了
test = test.add(5); // test 等于 11 了

注意 mul 和 add 其實都需要兩個參數(shù)。 在我們聲明了 using SafeMath for uint 后囊榜,我們用來調(diào)用這些方法的 uint 就自動被作為第一個參數(shù)傳遞進去了(在此例中就是 test)

我們來看看 add 的源代碼看 SafeMath 做了什么:

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}

基本上 add 只是像 + 一樣對兩個 uint 相加审胸, 但是它用一個 assert 語句來確保結(jié)果大于 a。這樣就防止了溢出卸勺。

assert和require區(qū)別

assertrequire 相似砂沛,若結(jié)果為否它就會拋出錯誤。 assert 和 require 區(qū)別在于曙求,require 若失敗則會返還給用戶剩下的 gas碍庵, assert 則不會。所以大部分情況下悟狱,你寫代碼的時候會比較喜歡 require静浴,assert 只在代碼可能出現(xiàn)嚴重錯誤的時候使用,比如 uint 溢出挤渐。

所以簡而言之苹享, SafeMath 的 add, sub浴麻, mul得问, 和 div 方法只做簡單的四則運算,然后在發(fā)生溢出或下溢的時候拋出錯誤软免。

在我們的代碼里使用SafeMath宫纬。

為了防止溢出和下溢,我們可以在我們的代碼里找 +膏萧, -漓骚, *宣蔚, 或 /,然后替換為 add, sub, mul, div.

比如认境,與其這樣做:

myUint++;

我們這樣做:

myUint = myUint.add(1);

實戰(zhàn)演練

ZombieOwnership 中有兩個地方用到了數(shù)學(xué)運算,來替換成 SafeMath 方法把挟鸠。

  • 1叉信、將 ++ 替換成 SafeMath 方法。
  • 2艘希、將 -- 替換成 SafeMath 方法硼身。

ZombieOwnership

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

contract ZombieOwnership is ZombieAttack, ERC721 {

  using SafeMath for uint256;

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    // 1\. 替換成 SafeMath 的 `add`
    // ownerZombieCount[_to].add(1);  // 這種寫法錯誤,沒有賦值
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);

    // 2\. 替換成 SafeMath 的 `sub`
    // ownerZombieCount[_from].sub(1); // 這種寫法錯誤
    ownerZombieCount[_from] = ownerZombieCount[_from].sub(1);

    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

  function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }

  function takeOwnership(uint256 _tokenId) public {
    require(zombieApprovals[_tokenId] == msg.sender);
    address owner = ownerOf(_tokenId);
    _transfer(owner, msg.sender, _tokenId);
  }
}

其他類型

太好了,這下我們的 ERC721 實現(xiàn)不會有溢出或者下溢了覆享。

回頭看看我們在之前課程寫的代碼佳遂,還有其他幾個地方也有可能導(dǎo)致溢出或下溢。

比如撒顿, 在 ZombieAttack 里面我們有:

myZombie.winCount++;
myZombie.level++;
enemyZombie.lossCount++;

我們同樣應(yīng)該在這些地方防止溢出丑罪。(通常情況下,總是使用 SafeMath 而不是普通數(shù)學(xué)運算是個好主意凤壁,也許在以后 Solidity 的新版本里這點會被默認實現(xiàn)吩屹,但是現(xiàn)在我們得自己在代碼里實現(xiàn)這些額外的安全措施)。

不過我們遇到個小問題 — winCount 和 lossCount 是 uint16拧抖, 而 level 是 uint32煤搜。 所以如果我們用這些作為參數(shù)傳入 SafeMath 的 add 方法。 它實際上并不會防止溢出唧席,因為它會把這些變量都轉(zhuǎn)換成 uint256:

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}

// 如果我們在`uint8` 上調(diào)用 `.add`擦盾。它將會被轉(zhuǎn)換成 `uint256`.
// 所以它不會在 2^8 時溢出,因為 256 是一個有效的 `uint256`.

這就意味著淌哟,我們需要再實現(xiàn)兩個庫來防止 uint16 和 uint32 溢出或下溢迹卢。我們可以將其命名為 SafeMath16SafeMath32

代碼將和 SafeMath 完全相同绞绒,除了所有的 uint256 實例都將被替換成 uint32 或 uint16婶希。

我們已經(jīng)將這些代碼幫你寫好了,打開 safemath.sol 合約看看代碼吧蓬衡。

現(xiàn)在我們需要在 ZombieFactory 里使用它們喻杈。

safemath.sol

pragma solidity ^0.4.18;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

/**
 * @title SafeMath32
 * @dev SafeMath library implemented for uint32
 */
library SafeMath32 {

  function mul(uint32 a, uint32 b) internal pure returns (uint32) {
    if (a == 0) {
      return 0;
    }
    uint32 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint32 a, uint32 b) internal pure returns (uint32) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint32 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint32 a, uint32 b) internal pure returns (uint32) {
    assert(b <= a);
    return a - b;
  }

  function add(uint32 a, uint32 b) internal pure returns (uint32) {
    uint32 c = a + b;
    assert(c >= a);
    return c;
  }
}

/**
 * @title SafeMath16
 * @dev SafeMath library implemented for uint16
 */
library SafeMath16 {

  function mul(uint16 a, uint16 b) internal pure returns (uint16) {
    if (a == 0) {
      return 0;
    }
    uint16 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint16 a, uint16 b) internal pure returns (uint16) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint16 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint16 a, uint16 b) internal pure returns (uint16) {
    assert(b <= a);
    return a - b;
  }

  function add(uint16 a, uint16 b) internal pure returns (uint16) {
    uint16 c = a + b;
    assert(c >= a);
    return c;
  }
}

實戰(zhàn)演練

分配:

  • 1、聲明我們將為 uint32 使用SafeMath32狰晚。
  • 2筒饰、聲明我們將為 uint16 使用SafeMath16。
  • 3壁晒、在 ZombieFactory 里還有一處我們也應(yīng)該使用 SafeMath 的方法瓷们, 我們已經(jīng)在那里留了注釋提醒你。

zombiefactory.sol

pragma solidity ^0.4.19;

import "./ownable.sol";
import "./safemath.sol";

contract ZombieFactory is Ownable {

  using SafeMath for uint256;
  // 1\. 為 uint32 聲明 使用 SafeMath32
    using SafeMath32 for uint32;
  // 2\. 為 uint16 聲明 使用 SafeMath16
   using SafeMath16 for uint16;

  event NewZombie(uint zombieId, string name, uint dna);

  uint dnaDigits = 16;
  uint dnaModulus = 10 ** dnaDigits;
  uint cooldownTime = 1 days;

  struct Zombie {
    string name;
    uint dna;
    uint32 level;
    uint32 readyTime;
    uint16 winCount;
    uint16 lossCount;
  }

  Zombie[] public zombies;

  mapping (uint => address) public zombieToOwner;
  mapping (address => uint) ownerZombieCount;

  function _createZombie(string _name, uint _dna) internal {
    // 注意: 我們選擇不處理2038年問題,所以不用擔(dān)心 readyTime 的溢出
    // 反正在2038年我們的APP早完蛋了
    uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
    zombieToOwner[id] = msg.sender;
    // 3\. 在這里使用 SafeMath 的 `add` 方法:
    // ownerZombieCount[msg.sender]++;
    ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);
    NewZombie(id, _name, _dna);
  }

  function _generateRandomDna(string _str) private view returns (uint) {
    uint rand = uint(keccak256(_str));
    return rand % dnaModulus;
  }

  function createRandomZombie(string _name) public {
    require(ownerZombieCount[msg.sender] == 0);
    uint randDna = _generateRandomDna(_name);
    randDna = randDna - randDna % 100;
    _createZombie(_name, randDna);
  }

}

現(xiàn)在谬晕,讓我們也順手把zombieattack.sol文件里邊的方法也修改為safeMath 形式碘裕。

zombieattack.sol

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

  function randMod(uint _modulus) internal returns(uint) {
    // 這兒有一個
    randNonce = randNonce.add(1);
    return uint(keccak256(now, msg.sender, randNonce)) % _modulus;
  }

  function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      // 這里有三個
      myZombie.winCount = myZombie.winCount.add(1);
      myZombie.level = myZombie.level.add(1);
      enemyZombie.lossCount = enemyZombie.lossCount.add(1);
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else {
      // 這兒還有倆哦
      myZombie.lossCount = myZombie.lossCount.add(1);
      enemyZombie.winCount = enemyZombie.winCount.add(1);
      _triggerCooldown(myZombie);
    }
  }
}

三、注釋

尸游戲的 Solidity 代碼終于完成啦攒钳。

在以后的課程中帮孔,我們將學(xué)習(xí)如何將游戲部署到以太坊,以及如何和 Web3.js 交互不撑。

不過在你離開這節(jié)之前文兢,我們來談?wù)勅绾?給你的代碼添加注釋.

注釋語法

Solidity 里的注釋和 JavaScript 相同。在我們的課程中你已經(jīng)看到了不少單行注釋了:

// 這是一個單行注釋焕檬,可以理解為給自己或者別人看的筆記

只要在任何地方添加一個 // 就意味著你在注釋姆坚。如此簡單所以你應(yīng)該經(jīng)常這么做。

不過我們也知道你的想法:有時候單行注釋是不夠的实愚。畢竟你生來話癆兼呵。

contract CryptoZombies { 
  /* 這是一個多行注釋。我想對所有花時間來嘗試這個編程課程的人說聲謝謝腊敲。
  它是免費的萍程,并將永遠免費。但是我們依然傾注了我們的心血來讓它變得更好兔仰。

   要知道這依然只是區(qū)塊鏈開發(fā)的開始而已茫负,雖然我們已經(jīng)走了很遠,
   仍然有很多種方式來讓我們的社區(qū)變得更好乎赴。
   如果我們在哪個地方出了錯忍法,歡迎在我們的 github 提交 PR 或者 issue 來幫助我們改進:
    https://github.com/loomnetwork/cryptozombie-lessons

    或者,如果你有任何的想法榕吼、建議甚至僅僅想和我們打聲招呼饿序,歡迎來我們的電報群:
     https://t.me/loomnetworkcn
  */
}

所以我們有了多行注釋:

contract CryptoZombies { 
  /* 這是一個多行注釋。我想對所有花時間來嘗試這個編程課程的人說聲謝謝羹蚣。
  它是免費的原探,并將永遠免費。但是我們依然傾注了我們的心血來讓它變得更好顽素。

   要知道這依然只是區(qū)塊鏈開發(fā)的開始而已咽弦,雖然我們已經(jīng)走了很遠,
   仍然有很多種方式來讓我們的社區(qū)變得更好胁出。
   如果我們在哪個地方出了錯型型,歡迎在我們的 github 提交 PR 或者 issue 來幫助我們改進:
    https://github.com/loomnetwork/cryptozombie-lessons

    或者,如果你有任何的想法全蝶、建議甚至僅僅想和我們打聲招呼闹蒜,歡迎來我們的電報群:
     https://t.me/loomnetworkcn
  */
}

特別是寺枉,最好為你合約中每個方法添加注釋來解釋它的預(yù)期行為。這樣其他開發(fā)者(或者你自己绷落,在6個月以后再回到這個項目中)可以很快地理解你的代碼而不需要逐行閱讀所有代碼姥闪。

Solidity 社區(qū)所使用的一個標準是使用一種被稱作 natspec 的格式,看起來像這樣:

/// @title 一個簡單的基礎(chǔ)運算合約
/// @author H4XF13LD MORRIS
/// @notice 現(xiàn)在砌烁,這個合約只添加一個乘法
contract Math {
  /// @notice 兩個數(shù)相乘
  /// @param x 第一個 uint
  /// @param y  第二個 uint
  /// @return z  (x * y) 的結(jié)果
  /// @dev 現(xiàn)在這個方法不檢查溢出
  function multiply(uint x, uint y) returns (uint z) {
    // 這只是個普通的注釋甘畅,不會被 natspec 解釋
    z = x * y;
  }
}

@title(標題) 和 @author (作者)很直接了.

@notice (須知)向 用戶 解釋這個方法或者合約是做什么的。@dev (開發(fā)者) 是向開發(fā)者解釋更多的細節(jié)往弓。

@param (參數(shù))和 @return (返回) 用來描述這個方法需要傳入什么參數(shù)以及返回什么值。

注意你并不需要每次都用上所有的標簽蓄氧,它們都是可選的函似。不過最少,寫下一個 @dev 注釋來解釋每個方法是做什么的喉童。

實戰(zhàn)演練

ZombieOwnership 加上一些 natspec 標簽:

zombieownership.sol

pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

/// TODO: 把這里變成 natspec 標準的注釋把
/// @title 一個管理轉(zhuǎn)移僵尸所有權(quán)的合約
/// @author Corwien
/// @dev 符合 OpenZeppelin 對 ERC721 標準草案的實現(xiàn)
/// @date 2018/06/17
contract ZombieOwnership is ZombieAttack, ERC721 {

  using SafeMath for uint256;

  mapping (uint => address) zombieApprovals;

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
    ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

  function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }

  function takeOwnership(uint256 _tokenId) public {
    require(zombieApprovals[_tokenId] == msg.sender);
    address owner = ownerOf(_tokenId);
    _transfer(owner, msg.sender, _tokenId);
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撇寞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子堂氯,更是在濱河造成了極大的恐慌蔑担,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咽白,死亡現(xiàn)場離奇詭異啤握,居然都是意外死亡,警方通過查閱死者的電腦和手機晶框,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門排抬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人授段,你說我怎么就攤上這事蹲蒲。” “怎么了侵贵?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵届搁,是天一觀的道長。 經(jīng)常有香客問我窍育,道長卡睦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任漱抓,我火速辦了婚禮么翰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辽旋。我一直安慰自己浩嫌,他們只是感情好檐迟,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著码耐,像睡著了一般追迟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骚腥,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天敦间,我揣著相機與錄音,去河邊找鬼束铭。 笑死廓块,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的契沫。 我是一名探鬼主播带猴,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼懈万!你這毒婦竟也來了拴清?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤会通,失蹤者是張志新(化名)和其女友劉穎口予,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涕侈,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡沪停,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了裳涛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牙甫。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖调违,靈堂內(nèi)的尸體忽然破棺而出窟哺,到底是詐尸還是另有隱情,我是刑警寧澤技肩,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布且轨,位于F島的核電站,受9級特大地震影響虚婿,放射性物質(zhì)發(fā)生泄漏旋奢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一然痊、第九天 我趴在偏房一處隱蔽的房頂上張望至朗。 院中可真熱鬧,春花似錦剧浸、人聲如沸锹引。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫌变。三九已至吨艇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間腾啥,已是汗流浹背东涡。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倘待,地道東北人疮跑。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像凸舵,于是被迫代替她去往敵國和親祖娘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容