第三十课 以太坊智能合约solidity如何节省GAS费?

1,摘要

在以太坊上,代码即法律,交易即金钱。每一笔智能合约的运行,都要根据复杂度消耗一笔GAS费(ETH)。那么,智能合约solidity语言的编写,不仅要考虑安全,也要考虑语言的优化,以便高效便宜了。
本文将从以下一些方面分析如何节约GAS的编程总结:
1)如何在REMIX编译器上分析GAS/GAS LIMIT等信息
2) 如何优化节省GAS费用的方法

  • 创建合约优化
  • 存储优化
  • 变量排序优化
  • 交易输入数据优化
  • 转账优化
  • 部署合约优化
  • 调用合约函数的成本优化

2,如何在REMIX编译器上分析GAS/GAS LIMIT等信息

如果你想了解以太坊的账户、交易、Gas和Gas Limit等基本概念信息,可以阅读文章《以太坊的账户、交易、Gas和Gas Limit》
如果你不了解以太坊智能合约语言solidity编译IDE环境REMIX,可以阅读文章《第十课 Solidity语言编辑器REMIX指导大全》
本章节聚焦在如何通过REMIX编译器查看GAS/GAS LIMIT等信息。

2.1 简单智能合约样例

以太坊指令执行主要依靠GAS。当你执行智能合约时,它会消耗GAS。所以,如果你正在运行一个智能合约,那么每一条指令都要花费一定数量的GAS费。这有两个因素,即您发送的GAS数量和总区块GAS上限(a total block gas limit)。
举例来说,一个简单的智能合约,有一个保存无符号整数256值的函数。
合约代码如下:

pragma solidity ^0.4.19;
contract A {
    uint b;
    function saveB(uint _b) public {
        b = _b;
    }
}

如果你将此合约复制并粘贴到Remix中,则可以运行此合约。通过MIST或来自网站的MetaMask与此合同进行交互的方式类似。
让我们运行saveB(5)并查看日志窗口中发生的情况:

这儿有3个我们感兴趣的值:

  • GAS总量( "gas limit"): 3,000,000
  • 交易费用 ("transaction cost"): 41642 gas
  • 执行费用( "execution cost"): 20178 gas.

2.2 发送的GAS总量(Gas limit)


这儿显示的"Gas limit"是发送的GAS总量,Value是发给目标地址的ETH值。这2处的值可以被发送交易的用户修改。

2.3 交易成本(Transaction Cost)

交易成本,在Remix中显示,是实际交易成本加上执行成本的混合。我认为,这儿看起来有点误导。

如果您使用数据字段发送交易,那么交易包含一个基本成本和每个字节的附加成本(GAS计价)。看看以太坊黄纸的附录列出了每种的GAS费用:

一起来看看41642的交易成本是如何结合在一起的。这是Remix在交易中自动发送的数据字段:

input_remix

这儿是 Data-Field:

0x348218ec0000000000000000000000000000000000000000000000000000000000000005

数据字段是散列函数签名的前4个字节和32字节填充参数的组合。我们快速手动计算。
函数签名是saveB(uint256),如果我们用SHA3-256(或Keccak-256)散列函数,那么我们得到:348218ec5e13d72ab0b6b9db1556cba7b0b97f5626b126d748db81c97e97e43d如果我们取前4个字节(提醒:1个字节= 8位= 2个十六进制字符.1个十六进制字符= 4 bit = 0-15 = 0000到1111 = 0x0到0xF),然后我们得到348218ec。让我们0x在前面添加,我们得到0x348218ec。参数是一个256位的无符号整数,即32个字节。这意味着它将整数“5”填充到32个字节,换句话说,它将在数字前面添加63个零:
0000000000000000000000000000000000000000000000000000000000000005
从以太坊黄皮书上可以获得参考:

  • 每笔交易都有21000 GAS支付
  • 为交易的每个非零字节数据或代码支付68 GAS
  • 为交易的每个零字节数据或代码支付4 GAS

计算一下:
348218ec 是4个字节的数据,显然是非零的。
0000000000000000000000000000000000000000000000000000000000000005是31个字节的零数据和1个字节的非零数据的混合。
这使得总共5个字节的非零数据和31个字节的零数据。
(5 non-zero-bytes * 68 gas) + (31 zero-bytes * 4 gas) = 340 + 124 = 464 gas
对于我们的输入数据,我们必须支付464 GAS。除此之外,我们还要支付 21000 GAS,这是每笔交易支付的。因此总共需要21464用于交易。
让我们看看是否会增加。


Remix称“交易成本”为41642 gas,“执行成本”为 20178 gas。而在Remix中,“交易成本”实际上是交易成本加执行成本的总和。因此,如果我们从交易成本中减去执行成本,我们应该得到21464 gas。
41642 (交易成本”) - 20178 (执行成本) = 21464 gas
剩下的结果21464 gas为数据交易成本,同上计算公式。

2.4 执行成本(Execution Cost)

执行成本有点难以计算,因为发生了很多事情,辉哥试着告诉你合同执行时到底发生了什么。

让我们深入了解实际的事务并打开调试器。这可以通过单击事务旁边的“调试”按钮来完成。

可以打开指令折叠菜单和单步调试菜单。你将看到每一条指令以及每个指令在该特定步骤中花费的GAS费用。


这里看到的是所有以太坊汇编指令。因此,我们知道Solidity可以归结为EVM Assembly。这是矿工实际执行的智能合约运行看起来的实际情况。来看看前两个指令:

PUSH1 60
PUSH1 40

这意味着除了将值60和40推入堆栈之外别无其他。显然还有很多事情要做,你可以通过在单步调试器中移动蓝色滑块来完成它们的工作。
根据以太坊黄皮书将每个指令所需的确切气体量汇总在一起,以便将值5写入存储:

GAS Instruction
3   000 PUSH1 60
3   002 PUSH1 40
12  004 MSTORE
3   005 PUSH1 04
2   007 CALLDATASIZE
3   008 LT
3   009 PUSH1 3f
10  011 JUMPI
3   012 PUSH1 00
3   014 CALLDATALOAD
3   015 PUSH29 0100000000000000000000000000000000000000000000000000000000
3   045 SWAP1
5   046 DIV
3   047 PUSH4 ffffffff
3   052 AND
3   053 DUP1
3   054 PUSH4 348218ec
3   059 EQ
3   060 PUSH1 44
10  062 JUMPI
1   068 JUMPDEST
2   069 CALLVALUE
3   070 ISZERO
3   071 PUSH1 4e
10  073 JUMPI
3   074 PUSH1 00
3   076 DUP1
1   078 JUMPDEST
3   079 PUSH1 62
3   081 PUSH1 04
3   083 DUP1
3   084 DUP1
3   085 CALLDATALOAD
3   086 SWAP1
3   087 PUSH1 20
3   089 ADD
3   090 SWAP1
3   091 SWAP2
3   092 SWAP1
2   093 POP
2   094 POP
3   095 PUSH1 64
8   097 JUMP
1   100 JUMPDEST
3   101 DUP1
3   102 PUSH1 00
3   104 DUP2
3   105 SWAP1
20000   106 SSTORE
2   107 POP
2   108 POP
8   109 JUMP
1   098 JUMPDEST
0   099 STOP

合计为20178 GAS费。

2.5 GAS上限(Gas Limit)

所以,以太坊区块链上的每一条指令都会消耗一些GAS。如果你要将值写入存储,则需要花费很多。如果你只是使用堆栈,它的成本会低一些。但基本上所有关于EVM的指令都需要GAS。这意味着智能合约只能做有限的事情,直到发送的GAS用完为止。在样例这种情况下,我们发送了300万 GAS费。
当您返回REMIX的单步调试器,点击第一步时,您会看到每个步骤剩余多少GAS。辉哥在第一步打开它:



它已经从我们发送的300万(从3,000,000 - 21464 = 2,978,536)中扣除的交易成本开始。(说明:21464是之前2.3章节执行的数据执行成本。)
一旦此计数器达到零,那么合约执行将立即停止,所有存储的值将被回滚,你将获得“Out of Gas”异常告警。

2.6 区块GAS上限(Block Gas Limit)

除了通过交易设置的气Gas Limit外,还有一个所谓的“区块上限”。这是你可以发送的最大GAS量。目前,在Main-Net,该值大概为8M左右。

2.7 GAS退款(Gas Refund)

Gas Limit有一个好处:你不必自己计算它。如果你向合约发送8M的GAS,它耗尽41642 GAS,可以退还其余部分。因此,发送远远超过必要的GAS总会节省下来的,其余的将自动退还到你的账号地址。

2.8 GAS价格(Gas Price)

GAS价格决定了交易在能否被包含在下一个被挖出的区块中。
当你发送交易时,你可以激励矿工接下来处理您的交易。这种激励就是GAS PRICE。矿工一旦挖出新区块,也会将交易纳入该区块。哪些交易被纳入下一个区块是由矿工确定的 - 但他很可能将GAS PRICE从高到低排序。

假设有15笔未完成的交易,但只有12笔交易可以进入下一个区块。5个20 Gwei,5个15 Gwei和5个 5Gwei的GAS PRICE。矿工很可能按此顺序选择交易:5 * 20 + 5 * 15 + 2 * 5 Gwei并将它们合并到下一个挖掘区块中。

因此,GAS Limit基本上决定了以太坊虚拟机可以执行的指令数量,而GAS Price决定了矿工选择此交易的可能性。

大多数钱包将标准GAS Price设定为20Gwei左右(0.00000002 ETH)。如果您正在执行上述合约,那么您将支付约60-70美分(美元分),当前汇率为1 ETH = 800美元。所以它根本不便宜。

幸运的是,在网络拥塞期间,您只需要更高的GAS PRICE,那是因为许多人尝试同时发送交易。如果网络没有拥挤,那么您不需要支付这么多GAS。EthGasStation网站评估目前的交易价格为4 Gwei足够 。所以,凭借这个小功能,只需要4 Gwei的GAS,它将是16美分左右,而不是65美分。一个巨大的差异。

3,如何优化节省GAS费用的方法

GAS消耗可参考以下两个表: 表格1表格2 。下面提供一下优化GAS消耗的方法。

3.1 创建合约

创建合约对应CREATE和CODECOPY这两条指令。在合约中创建另一个空合约消耗42,901个GAS(总共64,173个GAS)。如果直接部署空白合约,共有68,653个GAS。
如果包含实施,可能会有数十万甚至数百万的GAS。它应该是所有指令中最昂贵的。如果创建多个合约实例,则GAS消耗可能很大。

建议: 避免将合约用作数据存储。

不好的代码实现:

contract User {
  uint256 public amount;
  bool public isAdmin;
  function User(uint256 _amount, bool _isAdmin) {
    amount = _amount;
    isAdmin = _isAdmin;
  }
}

好的代码实现:

contract MyContract {
  mapping(address => uint256) amount;
  mapping(address => bool) isAdmin;
}

另一种OK的代码实现:

contract MyContract {
  struct {
    uint256 amount;
    bool isAdmin;
  }
mapping(address => User) users;
}

3.2 存储

对应于SSTORE指令。存储新数据需要20,000 GAS。修改数据需要5000 GAS。一个例外是将非零变量更改为零。我们稍后会讨论这个问题。

建议: 避免重复写入,最好一次在最后尽可能多地写入到存储变量。
不好的代码样例:

uint256 public count;
// ...
for (uint256 i = 0; i < 10; ++i) {
  // ...  
  ++count;
}

好的代码样例:

for (uint256 i = 0; i < 10; ++i) {
  // ...
}
count += 10;

3.3 变量排序对GAS的影响

你可能不知道变量声明的顺序也会影响Gas的消耗。
由于EVM操作都是以32字节为单位执行的,因此编译器将尝试将变量打包成32字节集进行访问,以减少访问时间。
但是,编译器不够智能,无法自动优化变量分组。它将静态大小的变量分组为32个字节的组。例如:

contract MyContract {
  uint64 public a;
  uint64 public b;
  uint64 public c;
  uint64 public d;
function test() {
    a = 1;
    b = 2;
    c = 3;
    d = 4;
  }
}

执行test()时,看起来已经存储了四个变量。由于这四个变量之和恰好是32个字节,因此实际执行了一个SSTORE。这只需要20,000 GAS。

再看下一个例子:

contract MyContract {
  uint64 public a;
  uint64 public b;
  byte e;
  uint64 public c;
  uint64 public d;

function test() {
    a = 1;
    b = 2;
    c = 3;
    d = 4;
  }
}

中间插入了另一个变数,结果造成a,b,e和c会被分为一组,d独立为一组。同样的test()造成两次写入,消耗40000 Gas。

最后再看一个例子:

contract MyContract {
  uint64 public a;
  uint64 public b;
  uint64 public c;
  uint64 public d;
function test() {
    a = 1;
    b = 2;
    // ... do something
    c = 3;
    d = 4;
  }
}

这与第一个例子的区别在于,在存储a和b之后,完成了其他事情,最后存储了c和d。结果这次将导致两次写入。因为当执行“执行某事”时,编译器确定打包操作已结束,然后发送写入。但是,由于第二次写入是同一组数据,因此认为它是被修改的。将消耗总共25,000个气体。

建议:
根据上述原则,我们可以很容易地知道如何处理它。

  • 正确的排序和分组
    将数据大小分组为32个字节,并将通常同时更新的变量放在一起。
    不好的代码例子:
contract MyContract {
  uint128 public hp;
  uint128 public maxHp;
  uint32 level;
  uint128 public mp;
  uint128 public maxMp;
}

好的例子:

contract MyContract {
  uint128 public hp;
  uint128 public mp;
  uint128 public maxHp;
  uint128 public maxMp;
  uint32 level;
}

这里我们假设hp和mp更频繁地更新,并且maxHp和maxMp更频繁地一起更新。

  • 尽量一次访问
    不好的代码例子:
function test() {
    hp = 1;
    // ... do something
    mp = 2;
  }

好的例子:

function test() {
    // ... do something
    hp = 1;
    mp = 2;
  }

这个规则在struct上是一样的。

3.4 交易输入数据

合约交易的基本气体是21,000。输入数据为每字节68个GAS,如果字节为0x00则为4个GAS。

例如,如果数据为0x0dbe671f,则气体为68 * 4 = 272; 如果是0x0000001f,它是68 * 1 + 4 * 3 = 80。

由于所有参数都是32字节,因此当参数为零时,气体消耗最小。它将是32 * 4 = 128。最大值如下:

n * 68 +(32-n)* 4 的字节数 (n:参数)

例如,32字节输入参数的最大GAS为2,176 (3268 = 2176)。输入参数为地址,地址是20个字节,因此它是1,408 (2068+(32-20)*4 = 1408)。

建议: 可以通过更改排序来节省GAS消耗。
例如EtherScan有下一段交易记录:

Function: trade(address tokenGet, uint256 amountGet, address tokenGive, uint256 amountGive, uint256 expires, uint256 nonce, address user, uint8 v, bytes32 r, bytes32 s, uint256 amount) ***
MethodID: 0x0a19b14a
[0]:0000000000000000000000000000000000000000000000000000000000000000
[1]:000000000000000000000000000000000000000000000000006a94d74f430000
[2]:000000000000000000000000a92f038e486768447291ec7277fff094421cbe1c
[3]:0000000000000000000000000000000000000000000000000000000005f5e100
[4]:000000000000000000000000000000000000000000000000000000000024cd39
[5]:00000000000000000000000000000000000000000000000000000000e053cefa
[6]:000000000000000000000000a11654ff00ed063c77ae35be6c1a95b91ad9586e
[7]:000000000000000000000000000000000000000000000000000000000000001c
[8]:caa3a70dd8ab2ea89736d7c12c6a8508f59b68590016ed99b40af0bcc2de8dee
[9]:26e2347abfba108444811ae5e6ead79c7bd0434cf680aa3102596f1ab855c571
[10]:000000000000000000000000000000000000000000000000000221b262dd8000

所有参数都是256位,无论类型是byte32,address还是uint8。所以左边的大多数参数都有大量的“0”是未使用的位。很容易想到使用这些“空间”。
例如可以把tokenGive的高位字节用于存放下面吗一些变量,把命名改为uint256 tokenSellWithData。

nonce  - > 40位
takerFee  - > 16位
makerFee  - > 16位
uint256 joyPrice  - > 28位
isBuy  - > 4位(实际上,1位就足够了。只是为了方便呈现文档)

假如上面变量的值分别为:

nonce: 0181bfeb
takerFee: 0014
makerFee: 000a
joyPrice: 0000000
isBuy: 1

那么tokenSellWithData的存储可能如:

更多优化参考文章《[Solidity] Compress input in smart contract》

3.5 转账

Call, send 和transfer 函数对应于CALL指令。基本消耗是7,400 GAS。事实上,消费将近7,600 GAS。值得注意的是,如果转账到一个从未见过的地址,将额外增加25,000个GAS。

没有额外的消耗样例:

function withdraw(uint256 amount){ 
  msg.sender.transfer(amount); 
}

可能会有额外的消耗样例(receiver参数未被使用,多余参数):

function withdrawTo(uint256 amount, address receiver) {
  receiver.transfer(amount);
}

3.6 其他命令

3.6.1 ecrecover

对应CALL指令。此功能将消耗3700 GAS。

3.6.2调用外部合约

调用外部合约执行EXTCODESIZE和CALL指令。基本消耗1400 GAS。除非必要,否则不建议拆分多个合同。可以使用多个继承来管理代码。

3.6.3事件

对应于LOG1指令。没有参数的事件是750 GAS。理论上每个附加参数将增加256个GAS,但事实上,它会更多。

3.6.3哈希

你可以使用智能合约中的几个内置哈希函数:keccak256,sha256和ripemd160。参数越多,消耗的气体越多。耗气量:ripemd160> sha256> keccak256。因此,如果没有其他目的,建议使用keccak256函数。

3.7 部署合约优化

大部分的优化在编译时候已经完成了。

问题:
部署合同中是否包含注释,是否会增加部署气体?
回答:
不,在编译期间删除了执行时不需要的所有内容。其中包括注释,变量名和类型名称。

并且可以在此处文章找到优化程序的详细信息。

另一种通过删除无用代码来减小大小的方法,。例如:

1 function p1 ( uint x ){ 
2    if ( x > 5)
3     if ( x*x < 20)
4        XXX }

在上面的代码中,第3行和第4行永远不会执行,并且可以避免这些类型的无用代码仔细通过合同逻辑,这将减少智能合约的大小。

3.8 调用合约函数的成本优化

当调用合约额的功能时,为了执行功能,它需要GAS。因此,优化使用较少GAS的功能非常重要。在考虑每个合约时时,可以采用多种不同的方式。这里有一些可能在执行过程中节省GAS的方法。

3.8.1 减少昂贵的操作

昂贵的操作是指一些需要更多GAS值的操作码,例如SSTORE。以下是一些减少昂贵操作的方法。

A)使用短路规则

操作符 || 和&&适用常见的短路规则。这意味着在表达式f(x)|| g(y)中,如果f(x)的计算结果为真,即使它有副作用,也不会评估g(y)。

因此,如果逻辑操作包括昂贵的操作和低成本操作,那么以昂贵的操作可以短路的方式安排将在一些执行中减少GAS。

如果f(x)是便宜的并且g(y)是昂贵的,逻辑运算代码(便宜的放在前面):

  • OR : f(x) || g(y)
  • AND: f(x) && g(y)

如果短路,将节省更多的气体。

f(x)g(y)安排AND操作相比,如果返回错误的概率要高得多,f(x) && g(y)可能会导致通过短路节省更多的气体。

f(x)g(y)安排OR运算相比,如果返回真值的概率要高得多,f(x) || g(y)可能会导致通过短路节省更多气体。

B)循环中昂贵的操作

不好的代码,例如:

 uint sum = 0;
 function p3 ( uint x ){
     for ( uint i = 0 ; i < x ; i++)
         sum += i; }

在上面的代码中,由于sum每次在循环内读取和写入存储变量,所以在每次迭代时都会发生昂贵的存储操作。这可以通过引入如下的局部变量来节省GAS来避免。
好的代码,例如:

 uint sum = 0;
 function p3 ( uint x ){
     uint temp = 0;
     for ( uint i = 0 ; i < x ; i++)
         temp += i; }
     sum += temp;

3.8.2 其他循环相关模式

循环组合,不好的代码样例:

function p5 ( uint x ){
    uint m = 0;
    uint v = 0;
    for ( uint i = 0 ; i < x ; i++) //loop-1
        m += i;
    for ( uint j = 0 ; j < x ; j++) /loop-2
        v -= j; }

loop-1和loop-2可以组合,可以节省燃气。
好的代码样例:

 function p5 ( uint x ){
    uint m = 0;
    uint v = 0;
    for ( uint i = 0 ; i < x ; i++) //loop-1
       m += i;
       v -= j; }

这里文章可以找到更多的循环模式

3.8.3 使用固定大小的字节数组

可以使用一个字节数组作为byte [],但它在传入调用时浪费了大量空间,每个元素31个字节。最好使用bytes。

根据经验,对任意长度的原始字节数据使用 bytes标识符,对任意长度的字符串(UTF-8)数据使用 string标识符。如果您可以将长度限制为特定的字节数,请始终使用bytes1到bytes32之一,因为它们要便宜得多。

具有固定长度总是节省GAS。也请参考这个问题描述

3.8.4 删除无用的代码可以在执行时节省GAS

如前面在合同部署中所解释的那样删除无用的代码即使在执行函数时也会节省GAS。

3.8.5 在实现功能时不使用库对于简单的使用来说更便宜。

调用库以获得简单的用法可能代价高昂。如果功能在合同中实现简单且可行,因为它避免了调用库的步骤。两种功能的执行成本仍然相同。

4, 参考

(1)区块链系列十九:Gas优化
(2)How to write an optimized (gas-cost) smart contract?
(3)[Solidity] Optimize Smart Contract Gas Usage
(4)What exactly is the Gas Limit and the Gas Price in Ethereum
(5)【易错概念】以太坊的账户、交易、Gas和Gas Limit的概念

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

推荐阅读更多精彩内容

  • 原文:Smart contracts 正如我们在[intro]中看到的那样,以太坊中有两种不同类型的帐户:外部拥有...
    Jisen阅读 4,909评论 1 7
  • 原文:Introduction 控制和责任 像以太坊这样的开放式区块链是安全的,因为它们是去中心化的。这意味着以太...
    Jisen阅读 6,222评论 0 7
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,178评论 4 8
  • 怎么对待生活,它也会怎么对你 人都是哭着来到这个美丽的人间。每个人从来到尘寰到升入天堂,整个生命的历程都是一本书,...
    静静在等你阅读 4,956评论 1 6