在上一篇文章中,我们通过一个实例了解了一个以太坊智能合约的基本结构、语法与一些概念,接下来我们将对其进行补充。
Gas:
当你激活一个智能合约的时候,你在要求整个网络内的每个矿工个体分别执行里面的运算。这会花费他们的时间和精力,Gas是你为这项服务向矿工们支付的机制。报酬是小额的以太币,想要运行智能合约的人的需要支付报酬来使合约工作。让智能合约花费Gas/以太币/钱可以防止人们随意激活合约,解决了垃圾交易以及相关问题,如果运行智能合约免费,此类问题会发生。
那么为什么不用ether直接支付费用呢,举个例子:现在执行一条语句需要0.0001ether,此后这个语句的调用一直保持0.0001ether,那么当之后ether涨了1000倍以后,执行成本就增加了1000倍,这就很不合理。现在我们就用gas这个概念把函数调用的消耗与ETH本身的价值相互结藕。我们此时再引入Gas price这个概念,付款款项(单位以太币)= Gas数量(单位Gas) x Gas price(单位以太币/Gas) 智能合约越复杂(计算步骤的数量和类型,占用的内存等),用来完成运行就需要越多Gas。
另外,任何特定的合约所需的运行合约的Gas数量是固定的,由合约的复杂度决定,而Gas价格由想运行合约的人规定,在他们提交运行合约请求的时候(有点类似于比特币的交易费)。每个矿工会根据Gas的价格的高低来决定他们是否想作为区块的一部分去运行此合约。如果你希望矿工运行你的合约,你最好提供高一点的Gas价格。
在某种程度是这是一场基于合约运行有多愿意付费驱动下的竞价。
如果你已经用remix测试过上一篇文中的合约,那么你就会发现Gas cost分为transaction cost和execution cost,如下图。
那么这两种cost以什么区别呢?
Execution cost包括存储全局变量以及方法调用相关的运行环境的开销。同一个函数,每次调用时的execution cost有可能是不同的。(比如全局变量发生了变化导致)
而transaction cost和编译后的合约代码长度相关,也和execution cost相关。同一个合约,每次执行时transaction cost - execution cost的值应该是不变的。
详情可以参见以太坊黄皮书附录G,以及这个链接:https://ethereum.stackexchange.com/questions/5812/what-is-the-difference-between-transaction-cost-and-execution-cost-in-browser-so
Contract调用时的Gas的支付方是谁?
Gas是由一开始发起transaction的地址进行支付。详细可以参http://ethfans.org/posts/797
solidity类型系统:
solidity是一个静态类型系统,也就是每个变量是什么类型的都要声明,这与C/C++类似,而与Python有所不同。
数据类型:
bool:false
/ true
操作符:!
, &&
, ||
, ==
, !=
uint / int:无符整型、有符整型
操作符:
<=
, <
, ==
, >=
, >
&
, |
, ^
, ~
+
, -
, *
, /
, %
, **
注意:solidity中没有浮点数,只有定点数,但是即使是定点数,在solidity目前版本的编译器中也是不支持的。
address:以太坊账户地址,是solidity中一种原生的比较复杂的类型(类比python中的dictionary、list)其中地址又可以分为两种类型:
普通账户:仅存储ETH的账户。
合约账户:既存储ETH,同时也有可以运行的代码,即智能合约。这些智能合约可以通过一个交易发送ETH的到账户里。一旦智能合约被上传,它就在那里等待被激活。
以太坊的地址是20字节的十六进制的bigNumber,范围在2^256以内,可以通过一下函数监测是否有效。
address x = 0xca35b7d915458ef540ade6068dfe2f4438fa733c;
function isValidAddress (address) {
return /^0x[0-9a-fA-F]{40}$/.test(address);
}
isValidAddress(x);
address的成员变量与函数:
address.balance
:地址余额,单位 Wei。balance 的值是 read only
的,调用 payable
函数入账,调用 address.transfer()
等出账,以太坊自动计算新的余额。address.transfer(uint256 value)
:给 address 转账 value(Wei),且调用异常会抛出。实践中转钱推荐使用transfer
。address.send(value)
:和 transfer 类似,但调用后的异常将不会被返回,只会返回一个布尔值。address.call
, address.callcode
, address.delegatecall
:用于智能合约之间调用彼此的函数。全局变量:
ETHER单位:1 wei
就等于integer中的1,其余类似快捷方式,当我们不想写1后面有18个0的时候写成1 ether
就可以了。所以1 ether
仍然代表一个数字而不代表钱。
wei
== 1szabo
== 10^12 wei
finney
== 10^15 wei
ether
== 10^18 wei
时间单位:1 seconds 代表数字 1,而不是时间的单位。与上面的ETHER类似,为了起到方便的作用。
seconds
== 1minutes
== 60 seconds
hours
== 60 minutes
days
== 24 hours
weeks
== 7 days
years
== 365 days
block:块,类比一下的话,block比较类似于singleton,它在整个程序中是唯一的,所有人都可以access。还可以类比为静态变量,它有自己的函数、方法、成员变量。
block.blockhash(uint blockNumber) returns (bytes32)
: 传入 blockNumber,返回块的哈希值。block.coinbase
(address
): 挖到当前块矿工的地址block.difficulty
(uint
): 当前块的难度block.gaslimit
(uint
): 目前block总的最多的gas是多少block.number
(uint
): 当前块是第几个block.timestamp
(uint
): 当前块创建的时间戳,传进来的unit是unix timestamp的基准(从1970-01-01开始)now
(uint
): block.timestamp 的快捷方式msg: 多用于当执行某一个函数的时候,这个函数的内部想要知道调用函数的数据信息。
msg.data
(bytes
): 包括函数名字等一些raw的信息,一些没有经过parse的信息msg.gas
(uint
): 函数调用方携带的 gas到这个函数调用的call里面msg.sender
(address
): 函数调用方的地址msg.sig
(bytes4
): 整个 msg.data
的前 4 个 byte
msg.value
(uint
): 函数调用方携带的 gas
,以 wei
为单位计价。补充:
constant
关键字:
在目前的版本中,constant
可以修饰变量和函数,但是在修饰函数时,是完全没有效果的,只能起到视觉上的警示,编译器并不会对函数是否改变成员变量做任何检查。尤其是在合作编程时,一定要注意这一点。可用以下例子测试:
pragma solidity ^0.4.0
contract simpleStorage(){
uint storedData;
function set(uint x) {
storedData = x;
}
function get() constant returns (uint) {
storedData = 1;
return storedData;
}
}
被动调用的编程模式:
具体来说就是,在ethereum中调用一个函数,这个函数一定是执行完成的,而不会等待一段时间之后自发再去进行一系列操作,一定是每一个函数都在一个区块中就完成了。因此原生定时器在ethereum是无法实现的,想要实现只能通过一些外部的服务。
因此在上一篇文章中,我们无法用原生定时器实现工资的个按时发放,要实现给员工发工资的功能就要把一个智能合约作为一个中介,这就是在区块链上一个很常见的设计范式。
调用节省gas的小技巧:
this.func()
,但是最后把this去掉。因为this
这种调用,在evm底层中是通过message实现的,这种情况下代价比较高,需要支付高昂的gas费用。用非this调用的话,是evm上的一个jump操作,这种方式的gas就会低很多。变量的作用域:
与Javascript很像,和C++,JAVA不同。只要在函数中定义了局部变量,作用域为整个函数。比如,在if内定义一个变量,在if内肯定有效,但在if外面也有效。
调试小技巧:
变量可以设置为public,这样在部署以后会有一个同名变量的按钮可以调用查看变量属性。
例如我将上一篇文章的代码中的owner加上public修饰:
另外remix下半部的debug按钮或者右边栏的debugger选项都可以帮助我们进行debug。
参考资料: