Solidity(4)

来源:互联网 发布:暴雨网络天书九卷官网 编辑:程序博客网 时间:2024/05/19 05:32

1. 单位和全局可用变量(Units and Globally Available Variables)

1.1 货币单位

wei,finney,szabo,ether。

1.2 时间单位

seconds,minutes,hours,days,weeks,years均可做为后缀,并进行相互转换,默认是seconds为单位。

默认规则如下:

1 == 1 seconds1 minutes == 60 seconds1 hours == 60 minutes1 days == 24 hours1 weeks = 7 days1 years = 365 days

后缀不能用于变量。

1.3 特殊变量和方程

在全局命名空间中存在一些特殊的变量和函数,它们主要用于提供关于区块链的信息。

1.3.1 块和事务

block.blockhash(uint blockNumber) returns (bytes32),给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。block.coinbase (address) 当前块矿工的地址。block.difficulty (uint)当前块的难度。 block.gaslimit (uint)当前块的gaslimit。 block.number (uint)当前区块的块号。 block.timestamp (uint)当前块的时间戳。 msg.data (bytes)完整的调用数据(calldata)。 msg.gas (uint)当前还剩的gas。 msg.sender (address)当前调用发起人的地址。 msg.sig (bytes4)调用数据的前四个字节(函数标识符)。 msg.value (uint)这个消息所附带的货币量,单位为wei。 now (uint)当前块的时间戳,等同于block.timestamp tx.gasprice (uint) 交易的gas价格。 tx.origin (address)交易的发送者(完整的调用链) 

msg的所有成员值,如msg.sender,msg.value的值可以因为每一次外部函数调用,或库函数调用发生变化(因为msg就是和调用相关的全局变量)。

如果你想在库函数中,用msg.sender实现访问控制,你需要将msg.sender做为参数(就是说不能使用默认的msg.value,因为它可能被更改)。

为了可扩展性的原因,你只能查最近256个块,所有其它的将返回0.

1.3.2 错误处理

assert(bool condition) //如果条件不满足,throws(用于内部错误)require(bool condition) //如果条件不满足,throws(用于输入或外部组件中的错误。)revert() //中止执行并恢复状态更改

1.3.3 数学和加密函数

addmod(uint x, uint y, uint k) returns (uint) //计算(x + y) % k。加法支持任意的精度。但不超过环绕的2**256(加法mod)。mulmod(uint x, uint y, uint k) returns (uint) //计算(x * y) % k。乘法支持任意精度,但不超过环绕的2**256(乘法mod)。keccak256(...) returns (bytes32) // 使用以太坊的(Keccak-256)计算HASH值。紧密打包。sha3(...) returns (bytes32) //等同于keccak256()。紧密打包。sha256(...) returns (bytes32) //使用SHA-256计算HASH值。紧密打包。ripemd160(...) returns (bytes20) //使用RIPEMD-160计算HASH值。紧密打包。ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address) //通过椭圆曲线签名信息恢复非对称加密算法公匙地址。如果出错会返回0,附录提供了一个例子.

例子:http://me.tryblockchain.org/web3js-sign-ecrecover-decode.html

紧密打包:参数不会补位,是连接的。这意味着以下内容都是相同的:

keccak256("ab", "c")keccak256("abc")keccak256(0x616263) //hexkeccak256(6382179)keccak256(97, 98, 99) // ascii

如果需要补位,需要明确的类型转换,如keccak256(“\x00\x12”)等同于keccak256(uint16(0x12))

需要注意的是字面量会用,尽可能小的空间来存储它们。比如,keccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))

在私链(private blockchain)上运行sha256,ripemd160或ecrecover可能会出现Out-Of-Gas报错。因为它们实现了一种预编译的机制,但合约要在收到第一个消息后才会存在。向一个不存在的合约发送消息,非常昂贵,所以才会导致Out-Of-Gas的问题。一种解决办法是每个在你真正使用它们前,先发送1 wei到这些合约上来完成初始化。在官方和测试链上没有这个问题。

1.3.4 地址相关

<address>.balance (uint256)//Address的余额,以wei为单位。<address>.transfer(uint256 amount)//发送给定数量的ether,以wei为单位,到某个地址。失败时抛出异常。<address>.send(uint256 amount) returns (bool)//发送给定数量的ether,以wei为单位,到某个地址。失败时返回false。<address>.call(...) returns (bool)//发起底层的call调用。失败时返回false。<address>.callcode(...) returns (bool)//发起底层的callcode调用,失败时返回false。<address>.delegatecall(...) returns (bool)//发起底层的delegatecall调用,失败时返回false。

使用send方法需要注意,调用栈深不能超过1024,或gas不足,都将导致发送失败。使用为了保证你的ether安全,要始终检查返回结果。当用户取款时,使用transfer或使用最佳实践的模式。

不鼓励使用callcode,因为将来会被删除。

1.3.5 合约相关

this(当前合约的类型)//当前合约的类型,可以显式的转换为Addressselfdestruct(address recipt)//销毁当前合约,并把它所有资金发送到给定的地址。

另外,当前合约里的所有函数均可支持调用,包括当前函数本身。

参考文献:http://www.tryblockchain.org/Solidity-AddressRelated-%E5%9C%B0%E5%9D%80.html

2. 表达式和控制结构(Expressions and Control Structures)

与Javascript一样,函数可以将参数作为输入;与Javascript和C不同,它们也可以返回任意数量的参数作为输出。

2.1 输入参数和输出参数

(1)输入参数
与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称。一种可接受两个整型参数的函数如下:

pragma solidity ^0.4.16;contract Simple {    function taker(uint _a, uint _b) public pure {        // do something with _a and _b.    }}

(2)输出参数
在returns关键字后定义,语法类似变量的定义方式。下面的例子展示的是,返回两个输入参数的求和,乘积的实现:

pragma solidity ^0.4.16;contract Simple {    function arithmetics(uint _a, uint _b)        public        pure        returns (uint o_sum, uint o_product)    {        o_sum = _a + _b;        o_product = _a * _b;    }}

出参的的名字可以省略。返回的值,同样可以通过return关键字来指定。return也可以同时返回多个值,参见Returning Multiple Values。出参的默认值为0,如果没有明确被修改,它将一直是0。

入参和出参也可在函数体内用做表达式。它们也可被赋值。

(3)返回多个值(Returning Multiple Values)

当返回多个参数时,使用return (v0, v1, …, vn)。返回结果的数量需要与定义的一致。

2.2 控制结构

不支持switch和goto,支持if,else,while,do,for,break,continue,return,?:。

条件判断中的括号不可省略,但在单行语句中的大括号可以省略。

需要注意的是,这里没有像C语言,和javascript里的非Boolean类型的自动转换,比如if(1){…}在Solidity中是无效的。

2.3 函数调用

(1)内部函数调用
在当前的合约中,函数可以直接调用(内部调用方式),也可递归调用,:

pragma solidity ^0.4.16;contract C {    function g(uint a) public pure returns (uint ret) { return f(); }    function f() internal pure returns (uint ret) { return g(7) + f(); }}

这些函数调用在EVM中被翻译成简单的跳转指令。这样带来的一个好处是,当前的内存不会被回收。所以在一个内部调用时传递一个内存型引用效率将非常高。当然,仅仅是同一个合约的函数之间才可通过内部的方式进行调用。

(2)外部函数调用

表达式this.g(8);和c.g(2)(这里的c是一个合约实例)是外部调用函数的方式。实现上是通过一个消息调用,而不是直接通过EVM的指令跳转。需要注意的是,在合约的构造器中,不能使用this调用函数,因为当前合约还没有创建完成。

其它合约的函数必须通过外部的方式调用。对于一个外部调用,所有函数的参数必须要拷贝到内存中。

当调用其它合约的函数时,可以通过选项.value(),和.gas()来分别指定,要发送的ether量(以wei为单位),和gas值。

  • 看不懂。。。。
pragma solidity ^0.4.0;contract InfoFeed {    function info() payable returns (uint ret) {         return msg.value;    }}contract Consumer {    function deposit() payable returns (uint){        return msg.value;    }     function left() constant returns (uint){        return this.balance;    }    function callFeed(address addr) returns (uint) {         return InfoFeed(addr).info.value(1).gas(8000)();     }}

上面的代码中,我们首先调用deposit()为Consumer合约存入一定量的ether。然后调用callFeed()通过value(1)的方式,向InfoFeed合约的info()函数发送1ether。需要注意的是,如果不先充值,由于合约余额为0,余额不足会报错Invalid opcode1。

InfoFeed.info()函数,必须使用payable关键字,否则不能通过value()选项来接收ether。

代码InfoFeed(addr)进行了一个显示的类型转换,声明了我们确定知道给定的地址是InfoFeed类型。所以这里并不会执行构造器的初始化。显示的类型强制转换,需要极度小心,不要尝试调用一个你不知道类型的合约。

我们也可以使用function setFeed(InfoFeed _feed) { feed = _feed; }来直接进行赋值。.info.value(1).gas(8000)只是本地设置发送的数额和gas值,真正执行调用的是其后的括号.info.value(1).gas(8000)()。

如果被调用的合约不存在,或者是不包代码的帐户,或调用的合约产生了异常,或者gas不足,均会造成函数调用发生异常。

如果被调用的合约源码并不事前知道,和它们交互会有潜在的风险。当前合约会将自己的控制权交给被调用的合约,而对方几乎可以做任何事。即使被调用的合约是继承自一个已知的父合约,但继承的子合约仅仅被要求正确实现了接口。合约的实现,可以是任意的内容,由此会有风险。另外,准备好处理调用你自己系统中的其它合约,可能在第一调用结果未返回之前就返回了调用的合约。某种程度上意味着,被调用的合约可以改变调用合约的状态变量(state variable)来标记当前的状态。如,写一个函数,只有当状态变量(state variables)的值有对应的改变时,才调用外部函数,这样你的合约就不会有可重入性漏洞。

  • 还是没怎么看懂????exm?????

(3)命名参数调用和匿名函数参数

函数调用的参数,可以通过指定名字的方式调用,但可以以任意的顺序,使用方式是用{}括起来。但参数的类型和数量要与定义一致。

pragma solidity ^0.4.0;contract C {    function add(uint val1, uint val2) returns (uint) { return val1 + val2; }    function g() returns (uint){        // named arguments        return add({val2: 2, val1: 1});    }}

(4)省略函数参数名称

没有使用的参数名可以省略(一般常见于返回值)。这些名字在栈(stack)上存在,但不可访问。

pragma solidity ^0.4.0;contract C {    // omitted name for parameter    function func(uint k, uint) returns(uint) {        return k;    }}

参考:http://www.tryblockchain.org/Solidity-FunctionCalls-%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8.html
如何使用Remix向合约发送ether参见这里。http://me.tryblockchain.org/%E6%94%AF%E4%BB%98%E7%9B%B8%E5%85%B3.html

2.4 创建合约(Creating Contracts via)

合同可以使用new关键字创建新合约。所创建的合约的完整代码必须提前知道,因此不可能实现递归的依赖关系。

pragma solidity ^0.4.0;contract D {    uint x;    function D(uint a) public payable {        x = a;    }}contract C {    D d = new D(4); // will be executed as part of C's constructor    function createD(uint arg) public {        D newD = new D(arg);    }    function createAndEndowD(uint arg, uint amount) public payable {        // Send ether along with the creation        D newD = (new D).value(amount)(arg);    }}

正如在示例中所看到的,在使用. value()选项创建一个D实例时,它是可能的,但是不可能限制gas的数量。如果创建失败(由于堆栈不足,没有足够的余额或其他问题),则抛出一个异常。

2.5 表达式的执行顺序(Order of Evaluation of Expressions)

表达式的执行顺序没有指定(更正式地说,表达式树中一个节点的子节点的计算顺序没有指定,但是它们当然是在节点本身之前计算的)。我们仅仅保证语句(statements)按顺序执行和布尔表达式的短路运算的支持。有关更多信息,请参见操作符优先顺序。http://solidity.readthedocs.io/en/develop/miscellaneous.html#order

2.6 赋值(Assignment)

(1)Destructing Assignments and Returning Multip Values
Solidity内置支持元组(tuple),也就是说支持一个可能的完全不同类型组成的一个列表,数量上是固定的(Tuple一般指两个,还有个Triple一般指三个)。

这种内置结构可以同时返回多个结果,也可用于同时赋值给多个变量。

pragma solidity ^0.4.16;contract C {    uint[] data;    function f() public pure returns (uint, bool, uint) {        return (7, true, 2);    }    function g() public {        // Declares and assigns the variables. Specifying the type explicitly is not possible.        var (x, b, y) = f();        // Assigns to a pre-existing variable.        (x, y) = (2, 7);        // Common trick to swap values -- does not work for non-value storage types.        (x, y) = (y, x);        // Components can be left out (also for variable declarations).        // If the tuple ends in an empty component,        // the rest of the values are discarded.        (data.length,) = f(); // Sets the length to 7        // The same can be done on the left side.        (,data[3]) = f(); // Sets data[3] to 2        // Components can only be left out at the left-hand-side of assignments, with        // one exception:        (x,) = (1,);        // (1,) is the only way to specify a 1-component tuple, because (1) is        // equivalent to 1.    }}

(2)数组和结构体的复杂性
对于非值类型,比如数组和数组,赋值的语法有一些复杂。

  • 赋值给一个状态变量总是创建一个完全无关的拷贝。
  • 赋值给一个局部变量,仅对基本类型,如那些32字节以内的静态类型(static types),创建一份完全无关拷贝。
  • 如果是数据结构或者数组(包括bytes和string)类型,由状态变量赋值为一个局部变量,局部变量只是持有原始状态变量的一个引用。对这个局部变量再次赋值,并不会修改这个状态变量,只是修改了引用。但修改这个本地引用变量的成员值,会改变状态变量的值。

2.7 范围和声明(Scoping And Decarations)

一个被声明的变量将有一个初始的默认值,它的字节表示都是零。变量的“默认值”是典型的“零状态”类型。例如,bool的默认值是false;uint或int类型的默认值为0。对于静态大小的数组和bytes1到bytes32,每个单独的元素将被初始化为对应于其类型的默认值。最后,对于动态大小的数组、字节和字符串,默认值是一个空数组或字符串。

函数中声明的任何位置的变量将在整个函数的范围内,而不考虑声明的位置。因为Solidity使用了javascript的变量作用范围的规则。与常规语言语法从定义处开始,到当前块结束为止不同。由此,下述代码编译时会抛出一个异常,Identifier already declared。

pragma solidity ^0.4.0;contract ScopingErrors {    function scoping() {        uint i = 0;        while (i++ < 1) {            uint same1 = 0;        }        while (i++ < 2) {            uint same1 = 0;// Illegal, second declaration of same1        }    }    function minimalScoping() {        {            uint same2 = 0;        }        {            uint same2 = 0;// Illegal, second declaration of same2        }    }    function forLoopScoping() {        for (uint same3 = 0; same3 < 1; same3++) {        }        for (uint same3 = 0; same3 < 1; same3++) {// Illegal, second declaration of same3        }    }    function crossFunction(){       uint same1 = 0;//Illegal    }}

另外的,如果一个变量被声明了,它会在函数开始前被初始化为默认值。所以下述例子是合法的。

pragma solidity ^0.4.0;contract C{    function foo() returns (uint) {    // baz is implicitly initialized as 0    uint bar = 5;    if (true) {        bar += baz;    } else {        uint baz = 10;// never executes    }    return bar;// returns 5}}

2.8 Error handling: Assert, Require, Revert and Exceptions

Solidity使用状态恢复来处理异常错误。如果出现异常将撤消当前调用中对状态的所有更改(以及它的所有子调用),并向调用者发送一个错误。
函数assert和require可以用于检查条件,如果条件未满足,则抛出异常。
assert函数只能用于测试内部错误,并检查不变量。
require函数被用来确保满足有效的条件,比如输入,或者合约状态变量,或者验证从调用到外部合约的返回值。

还有另外两种触发异常的方法:revert函数可用于标记错误并恢复当前调用。throw类似revert()。

在版本0.4.13中,throw关键字被弃用,将来会被淘汰。

当在子调用中发生异常时,它们会自动“冒泡”(即抛出异常)。这些函数send,calll,delegatecall,callcode是个例外,异常时会返回false。

在以下示例中,可以看到如何使用require轻松检查输入条件以及assert如何用于内部错误检查:

pragma solidity ^0.4.0;contract Sharer {    function sendHalf(address addr) public payable returns (uint balance) {        require(msg.value % 2 == 0); // Only allow even numbers        uint balanceBeforeTransfer = this.balance;        addr.transfer(msg.value / 2);        // Since transfer throws an exception on failure and        // cannot call back here, there should be no way for us to        // still have half of the money.        assert(this.balance == balanceBeforeTransfer - msg.value / 2);        return this.balance;    }}

assert会在下列情况下抛出异常:

  • 在一个太大或负值的索引中访问数组(如:在x[i]数组中访问i>x.length或者i<0)
  • 用一个太大或负数的索引中访问固定长度的bytesN。
  • 除数为0(如:5/0 or 23%0)
  • 对一个二进制移动一个负的值。
  • 将一个值过大或负值转换为enum类型。
  • 调用一个零初始化的内部函数类型变量。
  • 如果你用一个值为false的参数来调用assert。

require在下面情况的时候抛出异常:

  • 调用throw
  • 调用require,但参数值为false。
  • 如果通过消息调用调用一个函数,但它没有正确完成(即耗尽了gas,没有匹配函数,或抛出异常本身),除非使用低级操作调用、send、delegate atecall或callcode。低级操作永远不会抛出异常,但通过返回false表示失败。
  • 使用new关键字创建了一个合约,但是合约创建并没有正确完成(请参阅上面的定义“不正确完成”)。
  • 使用外部函数调用时,被调用的对象并不包含代码。
  • 如果你的public的函数在没有payable关键字时,却尝试在接收ether(包括构造函数,和回退函数)。
  • 合约通过一个public的getter函数(public getter funciton)接收ether。
  • .transfer()执行失败

在内部,Solidity执行一个require样式异常的还原操作(指令0xfd),并执行无效操作(指令0xfe)来抛出一个assert样式的异常。在这两种情况下,这将导致EVM恢复对状态的所有更改。恢复的原因是没有安全的方法继续执行,因为预期的效果没有发生。因为我们希望保留事务的原子性,最安全的做法是还原所有更改,并使整个事务(或至少调用)没有影响。请注意,assert风格的异常会消耗调用所使用的所有gas,而require样式的异常将不会从释放中消耗任何gas。