4.1.6 Contracts

Contracts

值得花时间了解合同的所有内容。要开始,请查看此示例:

Contract Deployment Example

要运行此示例,需要安装一些额外的功能:

  • eth-tester提供的沙箱节点。您可以使用 pip install -U web3[tester] 进行安装。
  • solc solidity编译器。请参阅安装 Solidity Compiler
import json
import web3

from web3 import Web3
from solc import compile_source
from web3.contract import ConciseContract

# Solidity source code
contract_source_code = '''
pragma solidity ^0.4.21;

contract Greeter {
    string public greeting;

    function Greeter() public {
        greeting = 'Hello';
    }

    function setGreeting(string _greeting) public {
        greeting = _greeting;
    }

    function greet() view public returns (string) {
        return greeting;
    }
}
'''

compiled_sol = compile_source(contract_source_code) # Compiled source code
contract_interface = compiled_sol['<stdin>:Greeter']

# web3.py instance
w3 = Web3(Web3.EthereumTesterProvider())

# set pre-funded account as sender
w3.eth.defaultAccount = w3.eth.accounts[0]

# Instantiate and deploy contract
Greeter = w3.eth.contract(abi=contract_interface['abi'], bytecode=contract_interface['bin'])

# Submit the transaction that deploys the contract
tx_hash = Greeter.constructor().transact()

# Wait for the transaction to be mined, and get the transaction receipt
tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)

# Create the contract instance with the newly-deployed address
greeter = w3.eth.contract(
    address=tx_receipt.contractAddress,
    abi=contract_interface['abi'],
)

# Display the default greeting from the contract
print('Default contract greeting: {}'.format(
    greeter.functions.greet().call()
))

print('Setting the greeting to Nihao...')
tx_hash = greeter.functions.setGreeting('Nihao').transact()

# Wait for transaction to be mined...
w3.eth.waitForTransactionReceipt(tx_hash)

# Display the new greeting value
print('Updated contract greeting: {}'.format(
    greeter.functions.greet().call()
))

# When issuing a lot of reads, try this more concise reader:
reader = ConciseContract(greeter)
assert reader.greet() == "Nihao"

Contract Factories

这些工厂不打算直接初始化。而是使用 w3.eth.contract() 方法创建合约对象。默认情况下,合约工厂是 Contract。请参阅 ConciseContract 中的示例以指定备用工厂。

class web3.contract.Contract(address)

Contract 提供了一个默认接口,用于部署以及与以太坊智能合约交互。

address参数可以是十六进制地址或ENS名称,如 mycontract.eth

class web3.contract.ConciseContract(Contract())

Contract 的这种变体旨在实现更简洁的读访问,而不会使写访问更加冗长。这是以失去对 deploy()address 等属性的访问权为代价的。建议对这些用例使用classic Contract。为了清楚起见,ConciseContract 仅公开合约函数,并且 ConciseContract API不提供所有其他 Contract 类方法和属性。这包括但不限于 contract.addresscontract.abicontract.deploy()

通过将 Contract 实例传递给 ConciseContract 来创建此类合约:

>>> concise = ConciseContract(myContract)

此变体将所有方法作为 call 调用,因此如果经典 Contract 具有类似 contract.functions.owner().call() 的方法,则可以使用 concise.owner() 来调用它。

要访问发送交易或估计 gas,可以添加关键字参数,如下所示:

>>> concise.withdraw(amount, transact={'from': eth.accounts[1], 'gas': 100000, ...})

>>>  # which is equivalent to this transaction in the classic contract:

>>> contract.functions.withdraw(amount).transact({'from': eth.accounts[1], 'gas': 100000, ...})
class web3.contract.ImplicitContract(Contract())

这种变化反映了 ConciseContract,但它将所有方法作为交易而不是 call 来调用,因此如果经典 Contract 有类似 contract.functions.owner.transact() 的方法,则可以使用 implicit.owner() 来调用它。

通过将 Contract 实例传递给 ImplicitContract 来创建此类合约:

>>> concise = ImplicitContract(myContract)

Properties

每个合约工厂都公开以下属性。

Contract.address

十六进制编码的合同的20字节地址,或ENS名称。如果在工厂创建期间未提供,则可以为无。

Contract.abi

合约ABI数组。

Contract.bytecode

合约字节码字符串。如果在工厂创建期间未提供,则可以为 None

Contract.bytecode_runtime

合约字节码字符串的运行时部分。如果在工厂创建期间未提供,则可以为 None

Contract.functions

这提供了对合约函数作为属性的访问。例如:myContract.functions.MyMethod()。公开的合约函数是 ContractFunction 类型的类。

Contract.events

这提供了作为属性访问合约事件的权限。例如:myContract.events.MyEvent()。公开的合约事件是 ContractEvent 类型的类。

Methods

每个合约工厂都公开以下方法。

classmethod Contract.constructor(*args, **kwargs).transact(transaction=None)

通过发送新的公开交易来构建和部署合约。

如果提供的 transaction 应该是符合 web3.eth.sendTransaction(transaction) 方法的字典。此值可能不包含 datato

如果合约采用构造函数参数,则应将它们作为位置参数或关键字参数提供。

如果ABI中指定的任何参数是 address 类型,则它们将接受ENS名称。

如果未提供 gas 值,则将使用 web3.eth.estimateGas() 方法创建部署交易的 gas 值。

返回部署交易的交易哈希。

>>> deploy_txn = token_contract.constructor(web3.eth.coinbase, 12345).transact()
>>> txn_receipt = web3.eth.getTransactionReceipt(deploy_txn)
>>> txn_receipt['contractAddress']
'0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318'
classmethod Contract.constructor(*args, **kwargs).estimateGas(transaction=None)

估算构造和部署合约的 gas。

此方法与 Contract.constructor(*args,**kwargs).transact() 方法的行为相同,交易详细信息传递到函数调用的结尾部分,函数参数传递到第一部分。

返回消耗的 gas,可用作公开执行此交易的 gas 估算值。

返回部署合约所需的 gas。

>>> token_contract.constructor(web3.eth.coinbase, 12345).estimateGas()
12563
classmethod Contract.constructor(*args, **kwargs).buildTransaction(transaction=None)

构造合约部署交易字节码数据。

如果合约采用构造函数参数,则应将它们作为位置参数或关键字参数提供。

如果ABI中指定的任何args是地址类型,则它们将接受ENS名称。

返回可以传递给sendTransaction方法的交易字典。

>>> transaction = {
'gasPrice': w3.eth.gasPrice,
'chainId': None
}
>>> contract_data = token_contract.constructor(web3.eth.coinbase, 12345).buildTransaction(transaction)
>>> web3.eth.sendTransaction(contract_data)
Contract.events.<event name>.createFilter(fromBlock=block, toBlock=block, argument_filters={"arg1": "value"}, topics=[])

创建一个新的事件过滤器,即 web3.utils.filters.LogFilter 的一个实例。

fromBlock 是必填字段。定义起始区块(独占)过滤区块范围。它可以是起始区块编号,也可以是最后一个挖掘块的 latest,或者是 pending 的交易。在 fromBlock 的情况下,'latest' 和 'pending' 将 'latest' 或 'pending' 区块设置为起始过滤区块的静态值。toBlock 可选。默认为 latest。 。定义过滤器区块范围中的结束区块(包括)。特殊值 latestpending 设置动态范围,该范围始终包括过滤器上部区块范围的 latestpending 块。address 可选。默认合约地址。过滤器匹配从 address 发出的事件日志。 argument_filters,可选。期待参数名称和值的字典。提供事件日志时,会针对事件参数值进行过滤。事件参数既可以是索引的,也可以是非索引的。索引值可以转换为相应的主题参数。未使用索引的参数将使用正则表达式进行过滤。topics 可选,接受标准的 JSON-RPC 主题参数。有关 topic 参数的更多信息,请参阅 eth_newFilter 的JSON-RPC文档。

classmethod Contract.eventFilter(event_name, filter_params=None)

Warning

Contract.eventFilter() 已弃用 ,替换为 Contract.events.<event name>.createFilter()

创建一个新的 web3.utils.filters.LogFilter 实例。

event_name 参数应该是要筛选的合约事件的名称。

如果提供,filter_params 应该是一个字典,为日志条目指定其他过滤器。支持以下键。

  • filter: dictionary - (可选)字典键应该是 Event 参数的参数名称。字典值应该是要过滤的值,或者要过滤的值列表。值列表将匹配其参数与列表中的任何值匹配的日志条目。接受索引和未索引的事件参数。使用 filter 参数时,内部处理索引参数值到十六进制编码主题的处理。
  • fromBlock: integer/tag - (可选,默认:latest)整数区块编号,或最后一个已开采块的 latestpending 的交易,earliest 为最早的区块。
  • toBlock: integer/tag - (可选,默认:latest)整数区块编号,或最后一个已开采块的 latestpending 的交易,earliest 为最早的区块。
  • address: string or list of strings, each 20 Bytes - (可选)合约地址或日志应来自的地址列表。
    topics: list of 32 byte strings or null - (可选)应该用于过滤的主题数组,事件签名的 keccak 哈希作为第一项,其余项目作为十六进制编码的参数值。主题依赖于顺序。此参数也可以是主题列表的列表,在这种情况下,过滤将匹配任何提供的主题数组。当不希望通过 filter 参数依赖内部生成的主题列表时,此参数很有用。如果 filter 参数包含 topic,则 topics 将添加到从 filter 参数推断出的任何主题列表中。

event_name 指定的事件的事件主题将添加到 filter_params['topics'] 列表中。

如果此合约的 Contract.address 属性为 non-null,则合约地址将添加到 filter_params

classmethod Contract.deploy(transaction=None, args=None)

Warning

不推荐使用:不推荐使用此方法,而使用 `constructor()`,这提供了更大的灵活性。

构建并发送交易以部署合同。

如果提供的 transaction 应该是符合 web3.eth.sendTransaction(transaction) 方法的字典。此值可能不包含 datato

如果合约采用构造函数参数,则应通过 args 参数将其作为列表提供。

如果ABI中指定的任何 argsaddress 类型,则它们将接受ENS名称。

如果未提供 gas,则将使用 web3.eth.estimateGas() 方法创建部署交易的 gas

返回部署交易的交易哈希。

classmethod Contract.all_functions()

返回 Contract 中存在的所有函数的列表,其中每个函数都是 ContractFunction 的一个实例。

>>> contract.all_functions()
[<Function identity(uint256,bool)>, <Function identity(int256,bool)>]
classmethod Contract.get_function_by_signature(signature)

搜索具有匹配签名的独特函数。找到匹配项后返回 ContractFunction 的实例。如果未找到匹配项,则抛出 ValueError

>>> contract.get_function_by_signature('identity(uint256,bool)')
<Function identity(uint256,bool)>
classmethod Contract.find_functions_by_name(name)

搜索具有匹配名称的所有函数。返回匹配函数的列表,其中每个函数都是 ContractFunction 的实例。找不到匹配项时返回空列表。

>>> contract.find_functions_by_name('identity')
[<Function identity(uint256,bool)>, <Function identity(int256,bool)>]
classmethod Contract.get_function_by_name(name)

搜索具有匹配名称的独特函数。找到匹配项后返回 ContractFunction 的实例。如果未找到匹配项或者找到多个匹配项,则抛出 ValueError

>>> contract.get_function_by_name('unique_name')
<Function unique_name(uint256)>
classmethod Contract.get_function_by_selector(selector)

使用匹配选择器搜索不同的函数。选择器可以是十六进制字符串,bytes或int。找到匹配项后返回 ContractFunction 的实例。如果未找到匹配项,则抛出 ValueError

>>> contract.get_function_by_selector('0xac37eebb')
<Function identity(uint256)'>
>>> contract.get_function_by_selector(b'\xac7\xee\xbb')
<Function identity(uint256)'>
>>> contract.get_function_by_selector(0xac37eebb)
<Function identity(uint256)'>
classmethod Contract.find_functions_by_args(*args)

搜索具有匹配args的所有函数。返回匹配函数的列表,其中每个函数都是 ContractFunction 的实例。找不到匹配项时返回空列表。

>>> contract.find_functions_by_args(1, True)
[<Function identity(uint256,bool)>, <Function identity(int256,bool)>]
classmethod Contract.get_function_by_args(*args)

使用匹配的args搜索不同的函数。找到匹配项后返回 ContractFunction 的实例。如果未找到匹配项或者找到多个匹配项,则抛出 ValueError

Note

合约方法 *all_functions*,*get_function_by_signature*,*find_functions_by_name*,*get_function_by_name*,*get_function_by_selector*,*find_functions_by_args* 和 *get_function_by_args* 只能在向合约提供了abi时使用。

Note
Web3.py 拒绝具有多个具有相同选择器或签名的函数的合同的初始化。例如。 blockHashAddendsInexpansible(uint256)blockHashAskewLimitary(uint256) 具有相同的选择器值,等于 0x00000000。包含这两项功能的合约将被拒绝。

Invoke Ambiguous Contract Functions Example

下面是具有多个相同名称的函数的合约示例,并且参数有歧义。

>>> contract_source_code = '''
pragma solidity ^0.4.21;
contract AmbiguousDuo {
  function identity(uint256 input, bool uselessFlag) returns (uint256) {
    return input;
  }
  function identity(int256 input, bool uselessFlag) returns (int256) {
    return input;
  }
}
'''
# fast forward all the steps of compiling and deploying the contract.
>>> ambiguous_contract.functions.identity(1, True) # raises ValidationError

>>> identity_func = ambiguous_contract.get_function_by_signature('identity(uint256,bool)')
>>> identity_func(1, True)
<Function identity(uint256,bool) bound to (1, True)>
>>> identity_func(1, True).call()
1

Event Log Object

事件日志对象是一个python字典,包含以下键:

  • args: Dictionary - 来自事件的参数。
  • event: String - 事件名。
  • logIndex: Number - 区块中日志索引位置的整数。
  • transactionIndex: Number - 从中创建了交易索引位置日志的整数。
  • transactionHash: String, 32 Bytes - 此日志创建的交易的哈希值。
  • address: String, 32 Bytes - 此日志源自的合约地址。
  • blockHash: String, 32 Bytes -
    此日志所在区块的哈希值。当其挂起时为 null
  • blockNumber: Number -
    此日志所在的区块编号。当其挂起时为 null
>>> transfer_filter = my_token_contract.eventFilter('Transfer', {'filter': {'_from': '0xdc3a9db694bcdd55ebae4a89b22ac6d12b3f0c24'}})
>>> transfer_filter.get_new_entries()
[...]  # array of Event Log Objects that match the filter.

# wait a while...

>>> transfer_filter.get_new_entries()
[...]  # new events since the last call

>>> transfer_filter.get_all_entries()
[...]  # all events that match the filter.

Contract Functions

class web3.contract.ContractFunction

通过 Contract.functions 属性公开的命名函数属于 ContractFunction 类型。这个类不能直接使用,而是通过 Contract.functions

例如:

myContract = web3.eth.contract(address=contract_address, abi=contract_abi)
twentyone = myContract.functions.multiply7(3).call()

如果在变量中有函数名称,则可能更喜欢以下替代方法:

func_to_call = 'multiply7'
contract_func = myContract.functions[func_to_call]
twentyone = contract_func(3).call()

ContractFunction 提供与合约函数交互的方法。提供给合约函数子类的位置和关键字参数将用于通过签名查找合约函数,并在适用时转发给合约函数。

Methods

ContractFunction.transact(transaction)

通过发送新的公共交易来执行指定的函数。

请参阅以下调用:

myContract.functions.myMethod(*args, **kwargs).transact(transaction)

函数调用的第一部分 myMethod(*args, **kwargs) 根据名称和提供的参数选择适当的合约函数。参数可以作为位置参数,关键字参数或两者的混合提供。

此函数调用 transact(transaction) 的结尾部分采用单个参数,该参数应该是符合与 web3.eth.sendTransaction(transaction) 方法相同格式的python字典。该字典可能不包含密钥数据。

如果ABI中指定的任何 argskwargsaddress 类型,则它们将接受ENS名称。

如果未提供 gas 值,则将使用 web3.eth.estimateGas() 方法创建方法事务的 gas 值。

返回交易哈希。

>>> token_contract.functions.transfer(web3.eth.accounts[1], 12345).transact()
"0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd"
ContractFunction.call(transaction, block_identifier='latest')

调用合约函数,使用 eth_call API在本地执行交易。这不会创建新的公共交易。

请参阅以下调用:

myContract.functions.myMethod(*args, **kwargs).call(transaction)

此方法与 ContractFunction.transact() 方法的行为相同,交易详细信息传递到函数调用的结尾部分,函数参数传递到第一部分。

返回已执行函数的返回值。

>>> my_contract.functions.multiply7(3).call()
21
>>> token_contract.functions.myBalance().call({'from': web3.eth.coinbase})
12345  # the token balance for `web3.eth.coinbase`
>>> token_contract.functions.myBalance().call({'from': web3.eth.accounts[1]})
54321  # the token balance for the account `web3.eth.accounts[1]`

可以使用 block_identifier 在历史块中调用该方法。一些例子:

# You can call your contract method at a block number:
>>> token_contract.functions.myBalance().call(block_identifier=10)

# or a number of blocks back from pending,
# in this case, the block just before the latest block:
>>> token_contract.functions.myBalance().call(block_identifier=-2)

# or a block hash:
>>> token_contract.functions.myBalance().call(block_identifier='0x4ff4a38b278ab49f7739d3a4ed4e12714386a9fdf72192f2e8f7da7822f10b4d')
>>> token_contract.functions.myBalance().call(block_identifier=b'O\xf4\xa3\x8b\'\x8a\xb4\x9fw9\xd3\xa4\xedN\x12qC\x86\xa9\xfd\xf7!\x92\xf2\xe8\xf7\xdax"\xf1\x0bM')

# Latest is the default, so this is redundant:
>>> token_contract.functions.myBalance().call(block_identifier='latest')

# You can check the state after your pending transactions (if supported by your node):
>>> token_contract.functions.myBalance().call(block_identifier='pending')
ContractFunction.estimateGas(transaction)

调用合约函数,使用 eth_call API在本地执行交易。这不会创建新的公共交易。

请参阅以下调用:

myContract.functions.myMethod(*args, **kwargs).estimateGas(transaction)

此方法与 ContractFunction.transact() 方法的行为相同,交易详细信息传递到函数调用的结尾部分,函数参数传递到第一部分。

返回消耗的 gas 量,可用作公开执行此交易的 gas 估算值。

>>> my_contract.functions.multiply7(3).estimateGas()
42650
ContractFunction.buildTransaction(transaction)

根据指定的合约函数调用构建交易字典。

请参阅以下调用:

myContract.functions.myMethod(*args, **kwargs).buildTransaction(transaction)

此方法的行为与 Contract.transact() 方法相同,交易详细信息传递到函数调用的结尾部分,函数参数传递到第一部分。

Note

除非在函数调用的第一部分中指定,否则 *nonce* 不会作为交易字典的一部分返回:
>>> math_contract.functions.increment(5).buildTransaction({'nonce': 10})
可以使用 `getTransactionCount()` 来获取帐户的当前 nonce。因此,生成包含随机数的交易字典的快捷方式如下所示:
>>> math_contract.functions.increment(5).buildTransaction({'nonce': web3.eth.getTransactionCount('0xF5...')})

返回一个交易字典。然后可以使用 sendTransaction() 发送此交易字典。

此外,字典可以使用 signTransaction() 用于脱机交易签名。

>>> math_contract.functions.increment(5).buildTransaction({'gasPrice': 21000000000})
{
    'to': '0x6Bc272FCFcf89C14cebFC57B8f1543F5137F97dE',
    'data': '0x7cf5dab00000000000000000000000000000000000000000000000000000000000000005',
    'value': 0,
    'gas': 43242,
    'gasPrice': 21000000000,
    'chainId': 1
}

Fallback Function

Contract Factory还提供了一个与 fallback 函数交互的API,它支持四种方法,如普通函数:

Contract.fallback.call(transaction)

调用回退函数,使用 eth_call API在本地执行交易。这不会创建新的公共交易。

Contract.fallback.estimateGas(transaction)

调用回退功能并返回 gas 估算。

Contract.fallback.transact(transaction)

通过发送新的公共交易来执行回退功能。

Contract.fallback.buildTransaction(transaction)

根据合约回退函数调用构建交易字典。

Events

class web3.contract.ContractEvents

通过 Contract.events 属性公开的命名事件属于ContractEvents类型。这个类不能直接使用,而是通过 Contract.events

例如:

myContract = web3.eth.contract(address=contract_address, abi=contract_abi)
tx_hash = myContract.functions.myFunction().transact()
receipt = web3.eth.getTransactionReceipt(tx_hash)
myContract.events.myEvent().processReceipt(receipt)

ContractEvent 提供与合约事件交互的方法。提供给合约事件子类的位置和关键字参数将用于通过签名查找合约事件。

ContractEvents.myEvent(*args, **kwargs).processReceipt(transaction_receipt)

从交易收据中提取相关日志。

返回事件日志对象的元组,从事件(例如 myEvent)发出,带有解码输出。

>>> tx_hash = contract.functions.myFunction(12345).transact({'to':contract_address})
>>> tx_receipt = w3.eth.getTransactionReceipt(tx_hash)
>>> rich_logs = contract.events.myEvent().processReceipt(tx_receipt)
>>> rich_logs[0]['args']
{'myArg': 12345}

Utils

classmethod Contract.decode_function_input(data)

解码用于调用智能合约函数的交易数据,并将ContractFunction和已解码的参数作为 dict 返回。

项目源代码

项目源代码会逐步上传到 Github,地址为 https://github.com/windstamp/jsonrpc

Contributor

  1. Windstamp, https://github.com/windstamp

Reference

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

推荐阅读更多精彩内容