Solidity(5)

来源:互联网 发布:js一句话木马 编辑:程序博客网 时间:2024/05/18 00:29

1.合约

Solidity中合约有点类似面向对象语言中的类。合约中有用于数据持久化的状态变量(state variables),和可以操作他们的函数。调用另一个合约实例的函数时,会执行一个EVM函数调用,这个操作会切换执行时的上下文,这样,前一个合约的状态变量(state variables)就不能访问了。

1.1 创建合约

IDEs,例如Remix,UI更好。

通过使用JavaScript API web3. js,可以在Ethereum上以编程方式创建合约。该方法称作web3 . eth。

当创建合约时,它的构造函数(与契约同名的函数)将被执行一次。构造函数是可选的。只允许一个构造函数,这意味着不支持重载。

在内部,构造函数的参数在合同代码之后通过ABI编码,但是如果使用web3 . js,则不必关心这个问题。

如果一个合约想要创建另一个合约,那么创建的合约的源代码(和二进制)必须被创建者知道。这意味着循环创建依赖项是不可能的。

pragma solidity ^0.4.16;contract OwnedToken {    // TokenCreator is a contract type that is defined below.    // It is fine to reference it as long as it is not used    // to create a new contract.    TokenCreator creator;    address owner;    bytes32 name;    // This is the constructor which registers the    // creator and the assigned name.    function OwnedToken(bytes32 _name) public {        // State variables are accessed via their name        // and not via e.g. this.owner. This also applies        // to functions and especially in the constructors,        // you can only call them like that ("internally"),        // because the contract itself does not exist yet.        owner = msg.sender;        // We do an explicit type conversion from `address`        // to `TokenCreator` and assume that the type of        // the calling contract is TokenCreator, there is        // no real way to check that.        creator = TokenCreator(msg.sender);        name = _name;    }    function changeName(bytes32 newName) public {        // Only the creator can alter the name --        // the comparison is possible since contracts        // are implicitly convertible to addresses.        if (msg.sender == address(creator))            name = newName;    }    function transfer(address newOwner) public {        // Only the current owner can transfer the token.        if (msg.sender != owner) return;        // We also want to ask the creator if the transfer        // is fine. Note that this calls a function of the        // contract defined below. If the call fails (e.g.        // due to out-of-gas), the execution here stops        // immediately.        if (creator.isTokenTransferOK(owner, newOwner))            owner = newOwner;    }}contract TokenCreator {    function createToken(bytes32 name)       public       returns (OwnedToken tokenAddress)    {        // Create a new Token contract and return its address.        // From the JavaScript side, the return type is simply        // "address", as this is the closest type available in        // the ABI.        return new OwnedToken(name);    }    function changeName(OwnedToken tokenAddress, bytes32 name)  public {        // Again, the external type of "tokenAddress" is        // simply "address".        tokenAddress.changeName(name);    }    function isTokenTransferOK(address currentOwner, address newOwner)        public        view        returns (bool ok)    {        // Check some arbitrary condition.        address tokenAddress = msg.sender;        return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);    }}

1.2 Visibility and Getters

1.2.1 可见性和权限控制

Solidity有两种函数调用方式,一种是内部调用,不会创建一个EVM调用(也叫做消息调用),另一种则是外部调用,会创建EVM调用(会发起消息调用)。Solidity对函数和状态变量提供了四种可见性。分别是external,public,internal,private。其中函数默认是public。状态变量默认的可见性是internal。

(1)external
外部函数是合约接口的一部分,所以我们可以从其它合约或通过交易来发起调用。一个外部函数f,不能通过内部的方式来发起调用,(如f()不可以,但可以通过this.f())。外部函数在接收大的数组数据时更加有效。

(2)internal
公开函数是合约接口的一部分,可以通过内部,或者消息来进行调用。对于public类型的状态变量,会自动创建一个访问器(详见下文)。

(3)private
私有函数和状态变量仅在当前合约中可以访问,在继承的合约内,不可访问。


所有在合约内的东西对外部的观察者来说都是可见,将某些东西标记为private仅仅阻止了其它合约来进行访问和修改,但并不能阻止其它人看到相关的信息。

可见性的标识符的定义位置,对于state variable是在类型后面,函数是在参数列表和返回关键字中间。来看一个定义的例子:

pragma solidity ^0.4.16;contract C {    function f(uint a) private pure returns (uint b) { return a + 1; }    function setData(uint a) internal { data = a; }    uint public data;}

在下面的例子中,D可以调用c.getData()来访问data的值,但不能调用f。合约E继承自C,所以它可以访问compute函数。

// This will not compilepragma solidity ^0.4.0;contract C {    uint private data;    function f(uint a) private returns(uint b) { return a + 1; }    function setData(uint a) public { data = a; }    function getData() public returns(uint) { return data; }    function compute(uint a, uint b) internal returns (uint) { return a+b; }}contract D {    function readData() public {        C c = new C();        uint local = c.f(7); // error: member "f" is not visible        c.setData(3);        local = c.getData();        local = c.compute(3, 5); // error: member "compute" is not visible    }}contract E is C {    function g() public {        C c = new C();        uint val = compute(3, 5); // access to internal member (from derived to parent contract)    }}

1.2.2 访问函数(Getter Function)

pragma solidity ^0.4.0;contract C{    uint public c = 10;}contract D{    C c = new C();    function getDataUsingAccessor() returns (uint){        return c.c();    }}

访问函数有外部(external)可见性。如果通过内部(internal)的方式访问,比如直接访问,你可以直接把它当一个变量进行使用,但如果使用外部(external)的方式来访问,如通过this.,那么它必须通过函数的方式来调用。

pragma solidity ^0.4.0;contract C{    uint public c = 10;    function accessInternal() returns (uint){        return c;    }    function accessExternal() returns (uint){        return this.c();    }}

在acessExternal函数中,如果直接返回return this.c;,会出现报错Return argument type function () constant external returns (uint256) is not implicitly convertible to expected type (type of first return variable) uint256.。原因应该是通过外部(external)的方式只能访问到this.c作为函数的对象,所以它认为你是想把一个函数转为uint故而报错。

参考文献:http://www.tryblockchain.org/Solidity-AccessorFunction-%E8%AE%BF%E9%97%AE%E5%87%BD%E6%95%B0.html

1.3 函数修改器

修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。

pragma solidity ^0.4.0;contract owned {    function owned() { owner = msg.sender; }    address owner;    // This contract only defines a modifier but does not use    // it - it will be used in derived contracts.    // The function body is inserted where the special symbol    // "_;" in the definition of a modifier appears.    // This means that if the owner calls this function, the    // function is executed and otherwise, an exception is    // thrown.    modifier onlyOwner {        if (msg.sender != owner)            throw;        _;    }}contract mortal is owned {    // This contract inherits the "onlyOwner"-modifier from    // "owned" and applies it to the "close"-function, which    // causes that calls to "close" only have an effect if    // they are made by the stored owner.    function close() onlyOwner {        selfdestruct(owner);    }}contract priced {    // Modifiers can receive arguments:    modifier costs(uint price) {        if (msg.value >= price) {            _;        }    }}contract Register is priced, owned {    mapping (address => bool) registeredAddresses;    uint price;    function Register(uint initialPrice) { price = initialPrice; }    // It is important to also provide the    // "payable" keyword here, otherwise the function will    // automatically reject all Ether sent to it.    function register() payable costs(price) {        registeredAddresses[msg.sender] = true;    }    function changePrice(uint _price) onlyOwner {        price = _price;    }}

这个合同只定义了一个修饰符,但是没有使用它 - 它将被用在派生的合同中。 函数体被插入特殊符号“_;” 在修饰符的定义中出现。 这意味着如果所有者调用这个函数,那么这个函数会被执行,否则会抛出一个异常。

修改器可以被继承,使用将modifier置于参数后,返回值前即可。

特殊_表示使用修改符的函数体的替换位置。

从合约Register可以看出全约可以多继承,通过,号分隔两个被继承的对象。

修改器也是可以接收参数的,如priced的costs。

使用修改器实现的一个防重复进入的例子。

pragma solidity ^0.4.0;contract Mutex {    bool locked;    modifier noReentrancy() {        if (locked) throw;        locked = true;        _;        locked = false;    }    /// This function is protected by a mutex, which means that    /// reentrant calls from within msg.sender.call cannot call f again.    /// The `return 7` statement assigns 7 to the return value but still    /// executes the statement `locked = false` in the modifier.    function f() noReentrancy returns (uint) {        if (!msg.sender.call()) throw;        return 7;    }}

例子中,由于call()方法有可能会调回当前方法,修改器实现了防重入的检查。

如果同一个函数有多个修改器,他们之间以空格隔开,修饰器会依次检查执行。

需要注意的是,在Solidity的早期版本中,有修改器的函数,它的return语句的行为有些不同。

在修改器中和函数体内的显式的return语句,仅仅跳出当前的修改器和函数体。返回的变量会被赋值,但整个执行逻辑会在前一个修改器后面定义的”_”后继续执行。

修改器的参数可以是任意表达式。在对应的上下文中,所有的函数中引入的符号,在修改器中均可见。但修改器中引入的符号在函数中不可见,因为它们有可能被重写。

1.4 合约状态变量

状态变量可以被定义为常量constant,这样的话,它必须在编译期间通过一个表达式赋值。赋值的表达式不允许:1)访问storage;2)区块链数据,如now,this.balance,block.number;3)合约执行的中间数据,如msg.gas;4)向外部合约发起调用。也许会造成内存分配副作用表达式是允许的,但不允许产生其它内存对象的副作用的表达式。内置的函数keccak256,keccak256,ripemd160,ecrecover,addmod,mulmod可以允许调用,即使它们是调用的外部合约。

允许内存分配,从而带来可能的副作用的原因是因为这将允许构建复杂的对象,比如,查找表。虽然当前的特性尚未完整支持。

编译器并不会为常量在storage上预留空间,每个使用的常量都会被对应的常量表达式所替换(也许优化器会直接替换为常量表达式的结果值)。

不是所有的类型都支持常量,当前支持的仅有值类型和字符串。

pragma solidity ^0.4.0;contract C {    uint constant x = 32**22 + 8;    string constant text = "abc";    bytes32 constant myHash = keccak256("abc");}

函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。

pragma solidity ^0.4.0;contract C {    function f(uint a, uint b) constant returns (uint) {        return a * (b + 42);    }}

访问器(Accessor)方法默认被标记为constant。当前编译器并未强制一个constant的方法不能修改状态。建议对于不会修改数据的标记为constant。

1.5 函数

(1)View 函数
函数可以被生命为view,但是这种情况下要保证不修改状态。
下面的情况被认为是修改状态:
1. 写入状态变量;
2. (emitting??)发送事件;
3. 创建其他合约;
4. 使用selfdestruct;
5. 通过调用发送Ether;
6. 调用没有被声明为view和pure的函数
7. 使用低级调用;
8. 使用包含特定操作码的内联程序集。

pragma solidity ^0.4.16;contract C {    function f(uint a, uint b) public view returns (uint) {        return a * (b + 42) + now;    }}

constant被认为是view的别名。
Getter方法被标记为view。

(2)pure函数

函数可以被声明为pure,不能修改状态不能读。

除了上面列出的情况,下面的情况也被认为是有读和修改状态的行为:
1. 读取状态变量;
2. 访问this.balance 或者<address>.balance;
3. 访问block,tx,msg(除了msg.sig和msg.data);
4. 调用没有标记为pure的函数;
5. 使用包含特定操作码的内联程序集。

pragma solidity ^0.4.16;contract C {    function f(uint a, uint b) public pure returns (uint) {        return a * (b + 42);    }}

(3)fallback函数

每一个合约有且仅有一个没有名字的函数。这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。

此外,当合约收到ether时(没有任何其它数据),这个函数也会被执行。在此时,一般仅有少量的gas剩余,用于执行这个函数(准确的说,还剩2300gas)。所以应该尽量保证回退函数使用少的gas。

下述提供给回退函数可执行的操作会比常规的花费得多一点。
1. 写入到存储(storage);
2. 创建一个合约;
3. 执行一个外部(external)函数调用,会花费非常多的gas;
4. 发送ether。

请确保在部署合同之前,彻底测试fallback函数,以确保执行成本小于2300。

一个没有定义一个回退函数的合约。如果接收ether,会触发异常,并返还ether(solidity v0.4.0开始)。所以合约要接收ether,必须实现回退函数。下面来看个例子:

pragma solidity ^0.4.0;contract Test {    // This function is called for all messages sent to    // this contract (there is no other function).    // Sending Ether to this contract will cause an exception,    // because the fallback function does not have the "payable"    // modifier.    //所有发送到此合同的消息都会调用此函数(没有其他函数)。 向此合约发送以太币将导致异常,因为后退功能没有“应付”修改器。    function() { x = 1; }    uint x;}// This contract keeps all Ether sent to it with no way// to get it back.contract Sink {    function() payable { }}contract Caller {    function callTest(Test test) {        test.call(0xabcdef01); // hash does not exist        // results in test.x becoming == 1.        // The following call will fail, reject the        // Ether and return false:        test.send(2 ether);    }}

在浏览器中跑的话,记得要先存ether。

(4)函数重载

一个合约可以有多个相同名称的函数,但可以使用不同的参数。这也适用于继承函数。下面的例子显示了合约A范围内f函数的重载。

pragma solidity ^0.4.16;contract A {    function f(uint _in) public pure returns (uint out) {        out = 1;    }    function f(uint _in, bytes32 _key) public pure returns (uint out) {        out = 2;    }}

在外部接口中也存在重载的函数。如果两个外部可见的函数不同于它们的Solidity类型,而不是它们的外部类型。

// This will not compilepragma solidity ^0.4.16;contract A {    function f(B _in) public pure returns (B out) {        out = _in;    }    function f(address _in) public pure returns (address out) {        out = _in;    }}contract B {}

以上两个函数重载都接受了ABI的地址类型,虽然它们在Solidity中被认为是不同的。

(5)重载解析和参数匹配

通过将当前范围内的函数声明与函数调用中提供的参数相匹配,可以选择重载函数。如果所有参数都可以隐式地转换为预期类型,则选择函数作为重载候选项。如果没有一个候选,解析失败。

返回参数没有考虑到重载解析。

pragma solidity ^0.4.16;contract A {    function f(uint8 _in) public pure returns (uint8 out) {        out = _in;    }    function f(uint256 _in) public pure returns (uint256 out) {        out = _in;    }}

调用f(50)会有一个类型错误,因为250可以隐式地转换为uint8和uint256类型。另一方面,f(256)将解析为f(uint256)重载,因为256不能隐式地转换为uint8。

1.6 事件

参考(可以说是复制粘贴):http://www.tryblockchain.org/Solidity-eventsAndLogs-%E4%BA%8B%E4%BB%B6%E4%B8%8E%E6%97%A5%E5%BF%97.html

事件是使用EVM日志内置功能的方便工具,在DAPP的接口中,它可以反过来调用Javascript的监听事件的回调。

事件在合约中可被继承。当被调用时,会触发参数存储到交易的日志中(一种区块链上的特殊数据结构)。这些日志与合约的地址关联,并合并到区块链中,只要区块可以访问就一直存在。日志和事件在合约内不可直接被访问,即使是创建日志的合约。

日志的SPV(简单支付验证)是可能的,如果一个外部的实体提供了一个这样证明的合约,它可以证明日志在区块链是否存在。但需要留意的是,由于合约中仅能访问最近的256个区块哈希,所以还需要提供区块头信息。

可以最多有三个参数被设置为indexed,来设置是否被索引。设置为索引后,可以允许通过这个参数来查找日志,甚至可以按特定的值过滤。

如果数组(包括string和bytes)类型被标记为索引项,会用它对应的Keccak-256哈希值做为topic。

除非是匿名事件,否则事件签名(比如:Deposit(address,hash256,uint256))是其中一个topic,同时也意味着对于匿名事件无法通过名字来过滤。

所有未被索引的参数将被做为日志的一部分被保存起来。

被索引的参数将不会保存它们自己,你可以搜索他们的值,但不能检索值本身。

pragma solidity ^0.4.0;contract ClientReceipt {    event Deposit(        address indexed _from,        bytes32 indexed _id,        uint _value    );    function deposit(bytes32 _id) {        // Any call to this function (even deeply nested) can        // be detected from the JavaScript API by filtering        // for `Deposit` to be called.        Deposit(msg.sender, _id, msg.value);    }}

下述是使用javascript来获取日志的例子。

var abi = /* abi as generated by the compiler */;var ClientReceipt = web3.eth.contract(abi);var clientReceipt = ClientReceipt.at(0x123 /* address */);var event = clientReceipt.Deposit();// watch for changesevent.watch(function(error, result){    // result will contain various information    // including the argumets given to the Deposit    // call.    if (!error)        console.log(result);});// Or pass a callback to start watching immediatelyvar event = clientReceipt.Deposit(function(error, result) {    if (!error)        console.log(result);});

通过函数log0,log1,log2,log3,log4,可以直接访问底层的日志组件。logi表示总共有带i + 1个参数(i表示的就是可带参数的数目,只是是从0开始计数的)。其中第一个参数会被用来做为日志的数据部分,其它的会做为主题(topics)。前面例子中的事件可改为如下:

log3(    msg.value,    0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,    msg.sender,    _id);

其中的长16进制串是事件的签名,计算方式是keccak256(“Deposit(address,hash256,uint256)”)

更对细节看上面的链接~(@^_^@)~

1.7 继承

Solidity通过复制代码包括多态性来支持多重继承。

所有函数调用是虚拟(virtual)的,这意味着最远的派生方式会被调用,除非明确指定了合约。

当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。

基本的继承体系与python有些类似,特别是在处理多继承上面。

下面用一个例子来详细说明:

pragma solidity ^0.4.0;contract owned {    function owned() { owner = msg.sender; }    address owner;}// Use "is" to derive from another contract. Derived// contracts can access all non-private members including// internal functions and state variables. These cannot be// accessed externally via `this`, though.//使用“is”从另一个合同派生。 派生合约可以访问所有非私有成员,包括内部函数和状态变量。 但是这些不能通过`this`从外部访问。contract mortal is owned {    function kill() {        if (msg.sender == owner) selfdestruct(owner);    }}// These abstract contracts are only provided to make the// interface known to the compiler. Note the function// without body. If a contract does not implement all// functions it can only be used as an interface.//这些抽象合约只是为了让编译器知道合约提供的接口。如果合约没有执行所有的函数,它只能被用作一个接口。contract Config {    function lookup(uint id) returns (address adr);}contract NameReg {    function register(bytes32 name);    function unregister(); }// Multiple inheritance is possible. Note that "owned" is// also a base class of "mortal", yet there is only a single// instance of "owned" (as for virtual inheritance in C++).contract named is owned, mortal {    function named(bytes32 name) {        Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);        NameReg(config.lookup(1)).register(name);    }    // Functions can be overridden by another function with the same name and    // the same number/types of inputs.  If the overriding function has different    // types of output parameters, that causes an error.    // Both local and message-based function calls take these overrides    // into account.    function kill() {        if (msg.sender == owner) {            Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);            NameReg(config.lookup(1)).unregister();            // It is still possible to call a specific            // overridden function.            mortal.kill();        }    }}// If a constructor takes an argument, it needs to be// provided in the header (or modifier-invocation-style at// the constructor of the derived //如果构造函数接受一个参数,则需要在头文件(或派生合约的构造函数中的修改器 - 调用-风格)中提供(参见下文)。contract (see below)).contract PriceFeed is owned, mortal, named("GoldFeed") {   function updateInfo(uint newInfo) {      if (msg.sender == owner) info = newInfo;   }   function get() constant returns(uint r) { return info; }   uint info;}

上面的例子的named合约的kill()方法中,我们调用了motal.kill()调用父合约的销毁函数(destruction)。但这样可能什么引发一些小问题。

pragma solidity ^0.4.0;contract mortal is owned {    function kill() {        if (msg.sender == owner) selfdestruct(owner);    }}contract Base1 is mortal {    function kill() { /* do cleanup 1 */ mortal.kill(); }}contract Base2 is mortal {    function kill() { /* do cleanup 2 */ mortal.kill(); }}contract Final is Base1, Base2 {}

对Final.kill()的调用只会调用Base2.kill(),因为派生重写,会跳过Base1.kill,因为它根本就不知道有Base1。一个变通方法是使用super。

pragma solidity ^0.4.0;contract mortal is owned {    function kill() {        if (msg.sender == owner) selfdestruct(owner);    }}contract Base1 is mortal {    function kill() { /* do cleanup 1 */ super.kill(); }}contract Base2 is mortal {    function kill() { /* do cleanup 2 */ super.kill(); }}contract Final is Base2, Base1 {}

如果Base1调用了函数super,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,所以会调用Base2.kill()。需要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这类似于普通虚函数查找(ordinary virtual method lookup)

  • 基类构造器的方法(Arguments for Base Constructors)

派生的合约需要提供所有父合约需要的所有参数,所以用两种方式来做,见下面的例子:

pragma solidity ^0.4.0;contract Base {    uint x;    function Base(uint _x) { x = _x; }}contract Derived is Base(7) {    function Derived(uint _y) Base(_y * _y) {    }}

或者直接在继承列表中使用is Base(7),或像修改器(modifier)使用方式一样,做为派生构造器定义头的一部分Base(_y * _y)。第一种方式对于构造器是常量的情况比较方便,可以大概说明合约的行为。第二种方式适用于构造的参数值由派生合约的指定的情况。在上述两种都用的情况下,第二种方式优先(一般情况只用其中一种方式就好了)。

  • 多继承与线性化(Multiple Inheritance and Linearization)
    链接:http://www.tryblockchain.org/Solidity-Inheritance-%E5%A4%9A%E7%BB%A7%E6%89%BF.html

1.8 抽象合约

http://www.tryblockchain.org/Solidity-AbstractContractsOrInterface-%E6%8E%A5%E5%8F%A3%E6%8A%BD%E8%B1%A1%E5%90%88%E7%BA%A6.html

1.9 接口

http://www.tryblockchain.org/Solidity-interface-%E6%8E%A5%E5%8F%A3.html

1.10 函数库

http://www.tryblockchain.org/solidity-libraries-%E5%BA%93.html

1.11 Using for

http://www.tryblockchain.org/solidity-libraries-%E5%BA%93.html

原创粉丝点击