使用Ink!開發(fā)Substrate ERC20智能合約

使用Ink!開發(fā)Substrate ERC20智能合約

jasonruan 2020.07.14

1 環(huán)境搭建

1.1 安裝Substrate節(jié)點(diǎn)

$ git clone git@github.com:paritytech/substrate.git
$ cd substrate
(master)$ git checkout -b v2.0.0-rc4 v2.0.0-rc4
切換到一個(gè)新分支 'v2.0.0-rc4'
(v2.0.0-rc4)$ cargo build --release

1.2 安裝cargo contract插件

  • 安裝命令
$ cargo install cargo-contract --vers 0.6.1 --force
  • 幫助手冊(cè)
$ cargo contract --help
cargo-contract 0.6.1
Utilities to develop Wasm smart contracts

USAGE:
    cargo contract <SUBCOMMAND>

OPTIONS:
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    new                  Setup and create a new smart contract project
    build                Compiles the smart contract
    generate-metadata    Generate contract metadata artifacts
    test                 Test the smart contract off-chain
    help                 Prints this message or the help of the given subcommand(s)

2 ERC20合約介紹

2.1 什么是ERC20標(biāo)準(zhǔn)

ERC20 通證標(biāo)準(zhǔn)(ERC20 Token Standard)是通過以太坊創(chuàng)建通證時(shí)的一種規(guī)范。按照 ERC20 的規(guī)范可以編寫一個(gè)智能合約,創(chuàng)建“可互換通證”。它并非強(qiáng)制要求亚茬,但遵循這個(gè)標(biāo)準(zhǔn)汞斧,所創(chuàng)建的通證可以與眾多交易所颜说、錢包等進(jìn)行交互哺壶,它現(xiàn)在已被行業(yè)普遍接受躲撰。

ERC20定義了一些標(biāo)準(zhǔn)的接口函數(shù):balanceOf 精偿、 totalSupply 泪酱、transfertransferFrom 还最、approveallowance 墓阀。 以及一些可選的字段,例如通證名稱拓轻、符號(hào)以及小數(shù)保留位數(shù)等斯撮。

image.png

詳見:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

2.2 ERC20接口

contract ERC20 {
   function totalSupply() constant returns (uint theTotalSupply);
   function balanceOf(address _owner) constant returns (uint balance);
   function transfer(address _to, uint _value) returns (bool success);
   function transferFrom(address _from, address _to, uint _value) returns (bool success);
   function approve(address _spender, uint _value) returns (bool success);
   function allowance(address _owner, address _spender) constant returns (uint remaining);
   event Transfer(address indexed _from, address indexed _to, uint _value);
   event Approval(address indexed _owner, address indexed _spender, uint _value);
}
  • 功能介紹:
函數(shù)名 功能
totalSupply 返回存在于流通中的通證(Token)總量
balanceOf 返回指定賬戶地址的通證余額
transfer 讓調(diào)用方將指定數(shù)量的通證發(fā)送到另一個(gè)地址,即轉(zhuǎn)賬
transferFrom 允許智能合約自動(dòng)執(zhí)行轉(zhuǎn)賬流程并代表所有者發(fā)送給定數(shù)量的通證
approve 調(diào)用方授權(quán)給定的地址可以從其地址中提款
allowance 返回被允許轉(zhuǎn)移的余額數(shù)量
event Transfer 事件通知扶叉,當(dāng)token被轉(zhuǎn)移時(shí)勿锅,必須調(diào)用觸發(fā),類似回調(diào)枣氧,當(dāng)事件發(fā)生時(shí)溢十,會(huì)得到通知
event Approval 事件通知,當(dāng)任何成功調(diào)用approve后达吞,必須調(diào)用觸發(fā)

3 ERC20合約開發(fā)

3.1 創(chuàng)建合約工程

執(zhí)行命令后张弛,會(huì)生成2個(gè)文件,其中lib.rs會(huì)包括一些基礎(chǔ)框架酪劫,我們可以在此基礎(chǔ)上開發(fā)我們的合約吞鸭。

$ cargo contract new erc20
    Created contract erc20
    
$ tree erc20/
erc20/
├── Cargo.toml
└── lib.rs

3.2 合約存儲(chǔ)創(chuàng)建

    #[ink(storage)]
    struct Erc20 {
        /// 代幣發(fā)行總量
        total_supply: storage::Value<Balance>,
        /// 用戶及余額映射
        balances: storage::HashMap<AccountId, Balance>,
    }

3.3 合約構(gòu)造方法創(chuàng)建

        #[ink(constructor)]
        fn new(&mut self, initial_supply: Balance) {
            // 獲取合約創(chuàng)建者
            let caller = self.env().caller();
            // 設(shè)置發(fā)行總量
            self.total_supply.set(initial_supply);
            // 合約創(chuàng)建者擁有所有發(fā)行代幣
            self.balances.insert(caller, initial_supply);
        }

3.4 合約接口方法創(chuàng)建

(1)查詢代幣發(fā)行總量接口

        #[ink(message)]
        fn total_supply(&self) -> Balance {
            *self.total_supply
        }

(2)查詢用戶代幣余額接口

        #[ink(message)]
        fn balance_of(&self, owner: AccountId) -> Balance {
            self.balance_of_or_zero(&owner)
        }

        // 工具方法:若用戶未被初始化,代幣余額置為0
        fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
            *self.balances.get(owner).unwrap_or(&0)

(3)轉(zhuǎn)賬接口

        #[ink(message)]
        fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
            // 獲取合約接口調(diào)用者地址
            let from = self.env().caller();
            // 給接收地址轉(zhuǎn)出指定金額代幣
            self.transfer_from_to(from, to, value)
        }

        fn transfer_from_to(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
            // 獲取合約調(diào)用者賬戶余額
            let from_balance = self.balance_of_or_zero(&from);
            if from_balance < value {
                return false
            }
            // 獲取合約接受者賬戶余額(代幣接收者賬戶可能未被初始化覆糟,通過此方法將其余額初始化為0)
            let to_balance = self.balance_of_or_zero(&to);
            // 發(fā)送者余額減少指定數(shù)量
            self.balances.insert(from, from_balance - value);
            // 接收者余額增加指定數(shù)量
            self.balances.insert(to, to_balance + value);
            true
        }

我們注意到刻剥,在進(jìn)行余額的增減時(shí),并未像以太坊的solidity智能合約滩字,使用額外的SafeMath接口造虏,這是因?yàn)?code>ink!提供了內(nèi)置防溢出保護(hù),通過在Cargo.toml 配置文件中麦箍,添加如下配置來提供該安全機(jī)制:

[profile.release]
panic = "abort"           <-- Panics shall be treated as aborts: reduces binary size
lto = true                <-- enable link-time-optimization: more efficient codegen
opt-level = "z"           <-- Optimize for small binary output
overflow-checks = true    <-- Arithmetic overflow protection

(4)授權(quán)轉(zhuǎn)賬——授權(quán)接口

通過授權(quán)轉(zhuǎn)賬漓藕,調(diào)用方可以授權(quán)指定賬戶,從其地址中安全的消費(fèi)指定數(shù)量的代幣内列。

需完善合約存儲(chǔ):

    #[ink(storage)]
    struct Erc20 {
        ......
        // (代幣所有者, 代幣授權(quán)使用者) -> 代幣授權(quán)使用者可支配余額
        allowances: storage::HashMap<(AccountId, AccountId), Balance>,
    }
        #[ink(message)]
        fn approve(&mut self, spender: AccountId, value: Balance) -> bool {
            let owner = self.env().caller();
            // 代幣所有者(owner)授權(quán)代幣使用者(spender)可支配余額(value)
            self.allowances.insert((owner, spender), value);
            true
        }

(5)授權(quán)轉(zhuǎn)賬——余額查詢

獲取代幣授權(quán)使用者剩余被允許轉(zhuǎn)移的代幣數(shù)量撵术。

        #[ink(message)]
        fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
            self.allowance_of_or_zero(&owner, &spender)
        }

(6)授權(quán)轉(zhuǎn)賬——轉(zhuǎn)賬接口

允許智能合約自動(dòng)執(zhí)行轉(zhuǎn)賬流程并代表所有者發(fā)送給定數(shù)量的代幣

        #[ink(message)]
        fn transfer_from(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
            let caller = self.env().caller();
            let allowance = self.allowance_of_or_zero(&from, &caller);
            if allowance < value {
                return false
            }
            self.allowances.insert((from, caller), allowance - value);
            self.transfer_from_to(from, to, value)
        }
        
        fn allowance_of_or_zero(&self, owner: &AccountId, spender: &AccountId) -> Balance {
            *self.allowances.get(&(*owner, *spender)).unwrap_or(&0)
        }

3.5 合約事件創(chuàng)建

  • 事件定義
    #[ink(event)]
    struct Transfer {
        #[ink(topic)]
        from: Option<AccountId>,
        #[ink(topic)]
        to: Option<AccountId>,
        #[ink(topic)]
        value: Balance,
    }

    #[ink(event)]
    struct Approval {
        #[ink(topic)]
        owner: AccountId,
        #[ink(topic)]
        spender: AccountId,
        #[ink(topic)]
        value: Balance,
    }
  • 合約構(gòu)造事件
            self.env().emit_event(Transfer {
                from: None,
                to: Some(caller),
                value: initial_supply,
            });
  • 轉(zhuǎn)賬事件
            self.env().emit_event(Transfer {
                from: Some(from),
                to: Some(to),
                value,
            });
  • 授權(quán)事件
            self.env().emit_event(Approval {
                owner,
                spender,
                value,
            });

3.6 單元測(cè)試用例編寫

        #[test]
        fn new_works() {
            let contract = Erc20::new(777);
            assert_eq!(contract.total_supply(), 777);
        }

        #[test]
        fn balance_works() {
            let contract = Erc20::new(100);
            assert_eq!(contract.total_supply(), 100);
            assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
            assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 0);
        }

        #[test]
        fn transfer_works() {
            let mut contract = Erc20::new(100);
            assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
            assert!(contract.transfer(AccountId::from([0x0; 32]), 10));
            assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
            assert!(!contract.transfer(AccountId::from([0x0; 32]), 100));
        }

        #[test]
        fn transfer_from_works() {
            let mut contract = Erc20::new(100);
            assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
            contract.approve(AccountId::from([0x1; 32]), 20);
            contract.transfer_from(AccountId::from([0x1; 32]), AccountId::from([0x0; 32]), 10);
            assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
        }

跑測(cè)試用例:

$ cargo +nightly test
image.png

3.7 完整代碼

#![cfg_attr(not(feature = "std"), no_std)]

use ink_lang as ink;

#[ink::contract(version = "0.1.0")]
mod erc20 {
    use ink_core::storage;

    #[ink(storage)]
    struct Erc20 {
        /// The total supply.
        total_supply: storage::Value<Balance>,
        /// The balance of each user.
        balances: storage::HashMap<AccountId, Balance>,
        /// Approval spender on behalf of the message's sender.
        allowances: storage::HashMap<(AccountId, AccountId), Balance>,
    }

    #[ink(event)]
    struct Transfer {
        #[ink(topic)]
        from: Option<AccountId>,
        #[ink(topic)]
        to: Option<AccountId>,
        #[ink(topic)]
        value: Balance,
    }

    #[ink(event)]
    struct Approval {
        #[ink(topic)]
        owner: AccountId,
        #[ink(topic)]
        spender: AccountId,
        #[ink(topic)]
        value: Balance,
    }

    impl Erc20 {
        #[ink(constructor)]
        fn new(&mut self, initial_supply: Balance) {
            let caller = self.env().caller();
            self.total_supply.set(initial_supply);
            self.balances.insert(caller, initial_supply);
            self.env().emit_event(Transfer {
                from: None,
                to: Some(caller),
                value: initial_supply,
            });
        }

        #[ink(message)]
        fn total_supply(&self) -> Balance {
            *self.total_supply
        }

        #[ink(message)]
        fn balance_of(&self, owner: AccountId) -> Balance {
            self.balance_of_or_zero(&owner)
        }

        #[ink(message)]
        fn approve(&mut self, spender: AccountId, value: Balance) -> bool {
            let owner = self.env().caller();
            self.allowances.insert((owner, spender), value);
            self.env().emit_event(Approval {
                owner,
                spender,
                value,
            });
            true
        }

        #[ink(message)]
        fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
            self.allowance_of_or_zero(&owner, &spender)
        }

        #[ink(message)]
        fn transfer_from(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
            let caller = self.env().caller();
            let allowance = self.allowance_of_or_zero(&from, &caller);
            if allowance < value {
                return false
            }
            self.allowances.insert((from, caller), allowance - value);
            self.transfer_from_to(from, to, value)
        }

        #[ink(message)]
        fn transfer(&mut self, to: AccountId, value: Balance) -> bool {
            let from = self.env().caller();
            self.transfer_from_to(from, to, value)
        }

        fn transfer_from_to(&mut self, from: AccountId, to: AccountId, value: Balance) -> bool {
            let from_balance = self.balance_of_or_zero(&from);
            if from_balance < value {
                return false
            }
            let to_balance = self.balance_of_or_zero(&to);
            self.balances.insert(from, from_balance - value);
            self.balances.insert(to, to_balance + value);
            self.env().emit_event(Transfer {
                from: Some(from),
                to: Some(to),
                value,
            });
            true
        }

        fn balance_of_or_zero(&self, owner: &AccountId) -> Balance {
            *self.balances.get(owner).unwrap_or(&0)
        }

        fn allowance_of_or_zero(&self, owner: &AccountId, spender: &AccountId) -> Balance {
            *self.allowances.get(&(*owner, *spender)).unwrap_or(&0)
        }
    }

    #[cfg(test)]
    mod tests {
        use super::*;

        #[test]
        fn new_works() {
            let contract = Erc20::new(777);
            assert_eq!(contract.total_supply(), 777);
        }

        #[test]
        fn balance_works() {
            let contract = Erc20::new(100);
            assert_eq!(contract.total_supply(), 100);
            assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
            assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 0);
        }

        #[test]
        fn transfer_works() {
            let mut contract = Erc20::new(100);
            assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
            assert!(contract.transfer(AccountId::from([0x0; 32]), 10));
            assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
            assert!(!contract.transfer(AccountId::from([0x0; 32]), 100));
        }

        #[test]
        fn transfer_from_works() {
            let mut contract = Erc20::new(100);
            assert_eq!(contract.balance_of(AccountId::from([0x1; 32])), 100);
            contract.approve(AccountId::from([0x1; 32]), 20);
            contract.transfer_from(AccountId::from([0x1; 32]), AccountId::from([0x0; 32]), 10);
            assert_eq!(contract.balance_of(AccountId::from([0x0; 32])), 10);
        }
    }
}

4 ERC20合約部署

4.1 啟動(dòng)substrate鏈

[Jason@RUAN:~/Blockchain/substrate] (v2.0.0-rc4)$ ./target/release/substrate purge-chain --dev
Are you sure to remove "/root/.local/share/substrate/chains/dev/db"? [y/N]: y
"/root/.local/share/substrate/chains/dev/db" did not exist.

[Jason@RUAN:~/Blockchain/substrate] (v2.0.0-rc4)$ ./target/release/substrate  --dev --ws-external --rpc-external --rpc-cors=all
2020-07-13 23:07:17 Substrate Node
2020-07-13 23:07:17 ??  version 2.0.0-rc4-00768a1-x86_64-linux-gnu
2020-07-13 23:07:17 ??  by Parity Technologies <admin@parity.io>, 2017-2020
2020-07-13 23:07:17 ?? Chain specification: Development
2020-07-13 23:07:17 ??  Node name: ill-hen-8567
2020-07-13 23:07:17 ?? Role: AUTHORITY
2020-07-13 23:07:17 ?? Database: RocksDb at /root/.local/share/substrate/chains/dev/db
2020-07-13 23:07:17 ?  Native runtime: node-254 (substrate-node-0.tx1.au10)
2020-07-13 23:07:17 ?? new validator set of size 1 has been elected via ElectionCompute::OnChain for era 0
2020-07-13 23:07:17 ?? Initializing Genesis block/state (state: 0xc720…bb8a, header-hash: 0x6ea2…1245)
2020-07-13 23:07:17 ?? Loading GRANDPA authority set from genesis on what appears to be first startup.
2020-07-13 23:07:17 ?  Loaded block-time = 3000 milliseconds from genesis on first-launch
2020-07-13 23:07:17 ?? Creating empty BABE epoch changes on what appears to be first startup.
2020-07-13 23:07:17 ?? Highest known block at #0
2020-07-13 23:07:17 Using default protocol ID "sup" because none is configured in the chain specs
2020-07-13 23:07:17 ??  Local node identity is: 12D3KooWQUQtujJ5SGCdCcheuExioC81R5W4E3RFGhmhx3MT8iqy (legacy representation: QmX71wUqWKy7FQX8PEHKoQLaiBLLTfK8TL25mFXxKhMWGw)
2020-07-13 23:07:17 ? Prometheus server started at 127.0.0.1:9615
2020-07-13 23:07:17 ?? Starting BABE Authorship worker
2020-07-13 23:07:18 ?? Starting consensus session on top of parent 0x6ea2a97a8da973976a82f053a8b909aff5e0659ca6d51b6c9d6947b4dc3d1245
2020-07-13 23:07:18 ?? Prepared block for proposing at 1 [hash: 0x3b99b664d0a21fbc72bfed709700b5bba05564c8d62e9ddd677412896f25de31; parent_hash: 0x6ea2…1245; extrinsics (1): [0xdcda…fb8d]]
2020-07-13 23:07:18 ?? Pre-sealed block for proposal at 1. Hash now 0x3081484a5cbe82a9b4a4aea4d360fd69219a43d18182c6fd297e2ffac71feff2, previously 0x3b99b664d0a21fbc72bfed709700b5bba05564c8d62e9ddd677412896f25de31.
2020-07-13 23:07:18 ?? New epoch 0 launching at block 0x3081…eff2 (block slot 531550946 >= start slot 531550946).
2020-07-13 23:07:18 ?? Next epoch starts at slot 531551146
2020-07-13 23:07:18 ? Imported #1 (0x3081…eff2)
2020-07-13 23:07:21 ?? Starting consensus session on top of parent 0x3081484a5cbe82a9b4a4aea4d360fd69219a43d18182c6fd297e2ffac71feff2
2020-07-13 23:07:21 ?? Prepared block for proposing at 2 [hash: 0x346204e0b46b86dc4ec85b18cf2fdf0f0e818b24208e56217e6f44c135e3aef3; parent_hash: 0x3081…eff2; extrinsics (1): [0xfdbb…bdd0]]
2020-07-13 23:07:21 ?? Pre-sealed block for proposal at 2. Hash now 0x906f64c7a6139ad0819f6c31d776404573e72f3f155bab486a9aeca7c89df810, previously 0x346204e0b46b86dc4ec85b18cf2fdf0f0e818b24208e56217e6f44c135e3aef3.
2020-07-13 23:07:21 ? Imported #2 (0x906f…f810)

4.2 合約編譯

$ cargo contract build
 [1/4] Collecting crate metadata
 [2/4] Building cargo project
    Finished release [optimized] target(s) in 0.05s
 [3/4] Post processing wasm file
 [4/4] Optimizing wasm file
wasm-opt is not installed. Install this tool on your system in order to 
reduce the size of your contract's Wasm binary. 
See https://github.com/WebAssembly/binaryen#tools
    
Your contract is ready. You can find it here:
./erc20/target/erc20.wasm

4.3 metadata生成

以便通過polkadot.js.org與合約進(jìn)行交互

$ cargo contract generate-metadata
  Generating metadata
    Updating git repository `https://github.com/paritytech/ink`
    Updating crates.io index
    Updating git repository `https://github.com/type-metadata/type-metadata.git`
    Finished release [optimized] target(s) in 3.38s
     Running `target/release/abi-gen`
    Your metadata file is ready.
You can find it here:
./erc20/target/metadata.json

4.4 上傳WASM

image.png
image.png
image.png

4.5 部署合約

image.png
image.png
image.png

5 ERC20合約執(zhí)行

5.1 執(zhí)行合約

image.png
image.png

注:右下角開關(guān)

打開開關(guān):作為RPC調(diào)用發(fā)送,只能查看鏈上狀態(tài)

關(guān)閉開關(guān):作為交易發(fā)送话瞧,對(duì)鏈上狀態(tài)有更改

5.2 查詢發(fā)行總量

image.png

5.3 查詢Alice賬戶余額

image.png

5.4 Alice給Bob轉(zhuǎn)賬1000

image.png

5.5 分別查詢Alice和Bob余額

image.png

5.6 Alice授權(quán)Eve可以消費(fèi)自己的2000代幣

image.png

5.7 Eve給Ferdie轉(zhuǎn)賬Alice的500代幣

image.png

5.8 查看到Ferdie的代幣數(shù)

image.png

5.9 查看Eve剩余Alice的授權(quán)額度

image.png

6 參考資料

https://substrate.dev/substrate-contracts-workshop/#/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嫩与,一起剝皮案震驚了整個(gè)濱河市寝姿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌划滋,老刑警劉巖饵筑,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異处坪,居然都是意外死亡根资,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門同窘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玄帕,“玉大人,你說我怎么就攤上這事想邦】阄疲” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵丧没,是天一觀的道長鹰椒。 經(jīng)常有香客問我,道長呕童,這世上最難降的妖魔是什么漆际? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮夺饲,結(jié)果婚禮上奸汇,老公的妹妹穿的比我還像新娘。我一直安慰自己钞支,他們只是感情好茫蛹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布操刀。 她就那樣靜靜地躺著烁挟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骨坑。 梳的紋絲不亂的頭發(fā)上撼嗓,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音欢唾,去河邊找鬼且警。 笑死,一個(gè)胖子當(dāng)著我的面吹牛礁遣,可吹牛的內(nèi)容都是我干的斑芜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼祟霍,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼杏头!你這毒婦竟也來了盈包?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤醇王,失蹤者是張志新(化名)和其女友劉穎呢燥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寓娩,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叛氨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了棘伴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寞埠。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖焊夸,靈堂內(nèi)的尸體忽然破棺而出畸裳,到底是詐尸還是另有隱情,我是刑警寧澤淳地,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布怖糊,位于F島的核電站,受9級(jí)特大地震影響颇象,放射性物質(zhì)發(fā)生泄漏伍伤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一遣钳、第九天 我趴在偏房一處隱蔽的房頂上張望扰魂。 院中可真熱鬧,春花似錦蕴茴、人聲如沸劝评。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蒋畜。三九已至,卻和暖如春撞叽,著一層夾襖步出監(jiān)牢的瞬間姻成,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工愿棋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留科展,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓糠雨,卻偏偏與公主長得像才睹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359