Solidity(4)

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

1.1 货币单位

wei,finney,szabo,ether。

1.2 时间单位

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

默认规则如下:

1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks = 7 days
1 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) //hex
keccak256(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 地址相关

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

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

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

1.3.5 合约相关

this(当前合约的类型)//当前合约的类型,可以显式的转换为Address

selfdestruct(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。

你可能感兴趣的:(区块链)