前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >solidity智能合约

solidity智能合约

作者头像
笔阁
发布2018-09-04 16:48:23
1.3K0
发布2018-09-04 16:48:23
举报
文章被收录于专栏:极客编程极客编程

智能合约

Solidity里的智能合约是面向对象语言里的类。它们持久存放在状态变量和函数中,(在里面)可以通过solidity修改这些变量。在不同的智能合约(实例)中调用一个函数(的过程),(实际上)是在EVM(Ether虚拟机)中完成一次调用,并且完成(一次)上下文切换,(此时)状态变量是不可访问的。

创建合约

      合约可以从“外部”创建,也可以由Solidity合约创立。在创建合约时,它的构造函数(函具有与合约名称同名的函数)将被执行。

   web3.js,即  JavaScript API, 是这样做的:

// The json abi array generated by the compiler

var abiArray = [

  {

    "inputs":[

      {"name":"x","type":"uint256"},

      {"name":"y","type":"uint256"}

    ],

    "type":"constructor"

  },

  {

    "constant":true,

    "inputs":[],

    "name":"x",

    "outputs":[{"name":"","type":"bytes32"}],

    "type":"function"

  }

];

var MyContract = web3.eth.contract(abiArray);// deploy new contractvar contractInstance = MyContract.new(

  10,

  {from: myAccount, gas: 1000000}

);

// The json abi array generated by the compiler  由编译器生成的json abi 数组

var abiArray = [

  {

    "inputs":[

      {"name":"x","type":"uint256"},

      {"name":"y","type":"uint256"}

    ],

    "type":"constructor"

  },

  {

    "constant":true,

    "inputs":[],

    "name":"x",

    "outputs":[{"name":"","type":"bytes32"}],

    "type":"function"

  }

];

var MyContract = web3.eth.contract(abiArray);

// deploy new contract  部署一个新合约

var contractInstance = MyContract.new(

  10,

  {from: myAccount, gas: 1000000}

);

在内部,在合约的代码后要接着有构造函数的参数,但如果你使用web3.js,就不必关心这个。

如果是一个合约要创立另外一个合约,被创立的合约的源码(二进制代码)要能被创立者知晓。这意味着:循环创建依赖就成为不可能的事情。

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) {

        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) {

        // Only the creator can alter the name --

        // the comparison is possible since contracts

        // are implicitly convertible to addresses.

        if (msg.sender == creator) name = newName;

    }

    function transfer(address newOwner) {

        // 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)

       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) {

        // Again, the external type of "tokenAddress" is

        // simply "address".

        tokenAddress.changeName(name);

    }

    function isTokenTransferOK(

        address currentOwner,

        address newOwner

    ) returns (bool ok) {

        // Check some arbitrary condition.

        address tokenAddress = msg.sender;

        return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);

    }}

contract OwnedToken {

    // TokenCreator is a contract type that is defined below.  TokenCreator是在下面定义的合约类型

    // 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) {

        owner = msg.sender;

        // We do an explicit type conversion from `address` 我们做一次由`address`到`TokenCreator` 的显示类型转换,,确保调用合约的类型是 TokenCreator, (因为没有真正的方法来检测这一点)

        // 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) {

        // Only the creator can alter the name --  仅仅是创立者可以改变名称--

        // the comparison is possible since contracts  因为合约是隐式转换到地址上,这种比较是可能的

        // are implicitly convertible to addresses.

        if (msg.sender == creator) name = newName;

    }

    function transfer(address newOwner) {

        // Only the current owner can transfer the token.  仅仅是 仅仅是当前(合约)所有者可以转移 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.     如果函数调用失败,(如gas用完了等原因)

        // due to out-of-gas), the execution here stops  程序的执行将立刻停止

        // immediately.

        if (creator.isTokenTransferOK(owner, newOwner))

            owner = newOwner;

    }}

contract TokenCreator {

    function createToken(bytes32 name)

       returns (OwnedToken tokenAddress)

    {

        // Create a new Token contract and return its address.  创立一个新的Token合约,并且返回它的地址

        // From the JavaScript side, the return type is simply  从 JavaScript观点看,返回的地址类型是"address"

        // "address", as this is the closest type available in   这个是和ABI最接近的类型

        // the ABI.

        return new OwnedToken(name);

    }

    function changeName(OwnedToken tokenAddress, bytes32 name) {

        // Again, the external type of "tokenAddress" is     "tokenAddress" 的外部类型也是 简单的"address".

        // simply "address".

        tokenAddress.changeName(name);

    }

    function isTokenTransferOK(

        address currentOwner,

        address newOwner

    ) returns (bool ok) {

        // Check some arbitrary condition. 检查各种条件

        address tokenAddress = msg.sender;

        return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);

    }

}

可见性和访问限制符

因为Solidity可以理解两种函数调用(“内部调用”,不创建一个真实的EVM调用(也称为“消息调用”);“外部的调用”-要创建一个真实的EMV调用),  有四种的函数和状态变量的可见性。

函数可以被定义为external, public, internal or private,缺省是 public。对状态变量而言, external是不可能的,默认是 internal。

external: 外部函数是合约接口的一部分,这意味着它们可以从其他合约调用, 也可以通过事务调用。外部函数f不能被内部调用(即 f()不执行,但this.f()执行)。外部函数,当他们接收大数组时,更有效。

**public:**公共函数是合约接口的一部分,可以通过内部调用或通过消息调用。对公共状态变量而言,会有的自动访问限制符的函数生成(见下文)。

**internal:**这些函数和状态变量只能内部访问(即在当前合约或由它派生的合约),而不使用(关键字)this 。

private:私有函数和状态变量仅仅在定义该合约中可见, 在派生的合约中不可见。

请注意

在外部观察者中,合约的内部的各项均可见。用 private 仅仅防止其他合约来访问和修改(该合约中)信息, 但它对blockchain之外的整个世界仍然可见。

可见性说明符是放在在状态变量的类型之后,(也可以放在)参数列表和函数返回的参数列表之间。

contract c {

    function f(uint a) private returns (uint b) { return a + 1; }

    function setData(uint a) internal { data = a; }

    uint public data;

}

其他合约可以调用c.data()来检索状态存储中data的值,但不能访问(函数)f。由c派生的合约可以访问(合约中)setData(函数),以便改变data的值(仅仅在它们自己的范围里)。

访问限制符函数

编译器会自动创建所有公共状态变量的访问限制符功能。下文中的合约中有一个称作data的函数,它不带任何参数的,它返回一个uint类型,  状态变量的值是data。可以在声明里进行状态变量的初始化。

访问限制符函数有外部可见性。如果标识符是内部可访问(即没有this),则它是一个状态变量,如果外部可访问的(即 有this),则它是一个函数。

contract test {

    uint public data = 42;}

下面的例子复杂些:

contract complex {

    struct Data { uint a; bytes3 b; mapping(uint => uint) map; }

    mapping(uint => mapping(bool => Data[])) public data;}

它生成了如下形式的函数:

function data(uint arg1, bool arg2, uint arg3) returns (uint a, bytes3 b){

    a = data[arg1][arg2][arg3].a;

    b = data[arg1][arg2][arg3].b;}

注意 结构体的映射省略了,因为没有好的方法来提供映射的键值。

函数修饰符

修饰符可以用来轻松改变函数的行为, 例如,在执行的函数之前自动检查条件。他们是可继承合约的属性,也可被派生的合约重写。

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; }

    function register() costs(price) {

        registeredAddresses[msg.sender] = true;

    }

    function changePrice(uint _price) onlyowner {

        price = _price;

    }}

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" 继承的"onlyowner"修饰符,

    // "owned" and applies it to the "close"-function, which    并且应用到"close"函数, 如果他们存储owner

    // 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; }

    function register() costs(price) {

        registeredAddresses[msg.sender] = true;

    }

    function changePrice(uint _price) onlyowner {

        price = _price;

    }

}

多个修饰符可以被应用到一个函数中(用空格隔开),并顺序地进行计算。当离开整个函数时,显式返回一个修饰词或函数体,  同时在“_”之后紧接着的修饰符,直到函数尾部的控制流,或者是修饰体将继续执行。任意表达式允许修改参数,在修饰符中,所有函数的标识符是可见的。在此函数由修饰符引入的标识符是不可见的(虽然他们可以通过重写,改变他们的值)。

常量

状态变量可以声明为常量(在数组和结构体类型上仍然不可以这样做,映射类型也不可以)。

contract C {

    uint constant x = 32*\*22 + 8;

    string constant text = "abc";

}

编译器不保留这些变量存储块,  每到执行到这个语句时,常量值又被替换一次。

表达式的值只能包含整数算术运算。

回退函数

一个合约可以有一个匿名函数。若没有其他函数和给定的函数标识符一致的话,该函数将没有参数,将执行一个合约的调用(如果没有提供数据)。

此外,当合约接收一个普通的Ether时,函数将被执行(没有数据)。在这样一个情况下,几乎没有gas用于函数调用,所以调用回退函数是非常廉价的,这点非常重要。

contract Test {

    function() { x = 1; }

    uint x;}

//  This contract rejects any Ether sent to it. It is good

// practise to  include such a function for every contract

// in order not to loose  Ether.

contract Rejector {

    function() { throw; }

}

contract Caller {

  function callTest(address testAddress) {

      Test(testAddress).call(0xabcdef01); // hash does not exist

      // results in Test(testAddress).x becoming == 1.

      Rejector r = Rejector(0x123);

      r.send(2 ether);

      // results in r.balance == 0

  }

}

contract Test {

    function() { x = 1; }

    uint x;}

//  This contract rejects any Ether sent to it. It is good   这个合约拒绝任何发给它的Ether.

// practise to  include such a function for every contract  为了严管Ether,在每个合约里包含一个这样的函数,是非常好的做法

// in order not to loose  Ether.

contract Rejector {

    function() { throw; }

}

contract Caller {

  function callTest(address testAddress) {

      Test(testAddress).call(0xabcdef01); // hash does not exist hash值不存在

      // results in Test(testAddress).x becoming == 1.  Test(testAddress).x的结果  becoming == 1

      Rejector r = Rejector(0x123);

      r.send(2 ether);

      // results in r.balance == 0     结果里r.balance == 0  

  }

}

事件

事件允许EMV写日志功能的方便使用, 进而在dapp的用户接口中用JavaScript顺序调用,从而监听这些事件。

事件是合约中可继承的成员。当他们调用时,会在导致一些参数在事务日志上的存储--在blockchain上的一种特殊的数据结构。这些日志和合约的地址相关联,  将被纳入blockchain中,存储在block里以便访问( 在Frontier 和** Homestead里是永久存储,但在Serenity**里有些变化)。在合约内部,日志和事件数据是不可访问的(从创建该日志的合约里)。

SPV日志证明是可行的, 如果一个外部实体提供一个这样的证明给合约,  它可以检查blockchain内实际存在的日志(但要注意这样一个事实,最终要提供block的headers, 因为合约只能看到最近的256块hash值)。

最多有三个参数可以接收属性索引,它将对各自的参数进行检索:  可以对用户界面中的索引参数的特定值进行过滤。

如果数组(包括string和 bytes)被用作索引参数,  就会以sha3-hash形式存储,而不是topic。

除了用anonymous声明事件之外,事件的指纹的hash值都将是topic之一。这意味着,不可能通过名字来过滤特定的匿名事件。

所有非索引参数将被作为数据日志记录的一部分进行存储。

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);

    }

}

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 任何对这个函数的调用都能通过JavaScipt API , 用`Deposit` 过滤来检索到(即使深入嵌套)

        // be detected from the JavaScript API by filtering

        // for `Deposit` to be called.

        Deposit(msg.sender, _id, msg.value);

    }

}

JavaScript API 的使用如下:

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 changes

event.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 immediately

var event = clientReceipt.Deposit(function(error, result) {

    if (!error)

        console.log(result);

});

var abi = /\ abi as generated by the compiler /;     /\ 由编译器生成的abi /;  

var ClientReceipt = web3.eth.contract(abi);

var clientReceipt = ClientReceipt.at(0x123 /\ address /);   /\ 地址 /); 

var event = clientReceipt.Deposit();

// watch for changes   观察变化

event.watch(function(error, result){

    // result will contain various information   结果包含不同的信息: 包括给Deposit调用的参数

    // including the argumets given to the Deposit

    // call.

    if (!error)

        console.log(result);});

// Or pass a callback to start watching immediately 或者通过callback立刻开始观察

var event = clientReceipt.Deposit(function(error, result) {

    if (!error)

        console.log(result);

});

底层日志的接口

还可以通过函数log0 log1,log2,log3 log4到 logi,共i+1个bytes32类型的参数来访问底层日志机制的接口。第一个参数将用于数据日志的一部分,其它的参数将用于topic。上面的事件调用可以以相同的方式执行。.

log3(

    msg.value,

    0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,

    msg.sender,

    _id

);

很长的十六进制数等于

sha3(“Deposit(address,hash256,uint256)”), 这个就是事件的指纹。

理解事件的额外的资源

继承

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

除非合约是显式给出的,所有的函数调用都是虚拟的,绝大多数派生函数可被调用。

即使合约是继承了多个其他合约, 在blockchain上只有一个合约被创建,  基本合约代码总是被复制到最终的合约上。

通用的继承机制非常类似于Python里的继承,特别是关于多重继承方面。

下面给出了详细的例子。

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.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, 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;

}

contract owned {

    function owned() { owner = msg.sender; }

    address owner;}

// Use "is" to derive from another contract. Derived   用"is"是从其他的合约里派生出

// contracts can access all non-private members including   派生出的合约能够访问所有非私有的成员,包括内部函数和状态变量。  它们不能从外部用'this'来访问。

// internal functions and state variables. These cannot be

// accessed externally via `this`, though.

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 多重继承也是可以的,注意"owned" 也是mortal的基类, 虽然 仅仅有"owned"的单个实例,(和C++里的virtual继承一样)

// 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, both local and  函数被重写,本地和基于消息的函数调用把这些override带入账户里。

    // 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  还可以调用特定的override函数

            // overridden function.

            mortal.kill();

        }

    }}

// If a constructor takes an argument, it needs to be  如果构造器里带有一个参数,有必要在头部给出,(或者在派生合约的构造器里使用修饰符调用方式modifier-invocation-style(见下文))

// 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;

}

注意:在上文中,我们使用mortal.kill() 来“forward” 析构请求。这种做法是有问题的,请看下面的例子:

contract mortal is owned {

    function kill() {

        if (msg.sender == owner) selfdestruct(owner);

    }

}

contract Base1 is mortal {

    function kill() { /\ do cleanup 1  清除1 / mortal.kill(); 

}

}

contract Base2 is mortal {

    function kill() { /\ do cleanup 2  清除2 / mortal.kill(); 

}

}

contract Final is Base1, Base2 {

}

 Final.kill() 将调用Base2.kill作为最后的派生重写,但这个函数绕开了Base1.kill。因为它不知道有Base1。这种情况下要使用 super

contract mortal is owned {

    function kill() {

        if (msg.sender == owner) selfdestruct(owner);

    }

}

contract Base1 is mortal {

    function kill() { /\ do cleanup 1   清除1  \/    super.kill(); }

}

contract Base2 is mortal {

    function kill() { /\ do cleanup 2  清除2  \/    super.kill(); }

}

contract Final is Base2, Base1 {

}

若Base1 调用了super函数,它不是简单地调用基本合约之一的函数, 它是调用最后继承关系的下一个基本合约的函数。所以它会调用 base2.kill()(注意,最后的继承顺序是–从最后的派生合约开始:Final, Base1, Base2, mortal, owned)。当使用类的上下文中super不知道的情况下,真正的函数将被调用,虽然它的类型已经知道。这个和普通的virtual方法的查找相似。

基本构造函数的参数

派生的合约需要为基本构造函数提供所有的参数。这可以在两处进行:

contract Base {

    uint x;

    function Base(uint _x) { x = _x;}

}

contract Derived is Base(7) {

    function Derived(uint _y) Base(_y * _y) {

    }

}

第一种方式是直接在继承列表里实现(是 Base(7)),第二种方式是在派生的构造器的头部,修饰符被调用时实现(Base(_y * _y))。如果构造函数参数是一个常量,并且定义了合约的行为或描述了它的行为,第一种方式比较方便。 如果基本构造函数参数依赖于派生合约的构造函数,则必须使用第二种方法。如果在这个荒谬的例子中,这两个地方都被使用,修饰符样式的参数优先。

多继承和线性化

允许多重继承的编程语言,要处理这样几个问题,其中一个是Diamond问题。Solidity是沿用Python的方式, 使用“C3线性化”,在基类的DAG强制使用特定的顺序。这导致单调但不允许有一些的继承关系。特别是,在其中的基础类的顺序是直接的,这点非常重要。在下面的代码中,Solidity会报错:“继承关系的线性化是不可能的”。

contract X {}

contract A is X {}

contract C is A, X {}

这个原因是,C要求X来重写A(定义A,X这个顺序),但A本身的要求重写X,这是一个矛盾,不能解决。

一个简单的规则是要指定基类中的顺序,从“最基本”到“最近派生”。

抽象契约

合约函数可以缺少实现(请注意,函数声明头将被终止),见下面的例子:

contract feline {

    function utterance() returns (bytes32);

}

这样的合约不能被编译(即使它们包含实现的函数和非实现的函数),但它们可以用作基本合约:

contract Cat is feline {

    function utterance() returns (bytes32) { return "miaow"; }

}

如果一个合约是从抽象合约中继承的,而不实现所有非执行功能,则它本身就是抽象的。

库和合约类似,但是它们的目的主要是在给定地址上部署,以及用EVM的CALLCODE特性来重用代码。这些代码是在调用合约的上下文里执行的,例如调用合约的指针和调用合约的存储能够被访问。由于库是一片独立的代码,如果它们显示地提供的话,就仅仅能访问到调用合约的状态变量(有方法命名它们)

下面的例子解释了怎样使用库(确保用using for 来实现)

library Set {

  // We define a new struct datatype that will be used to   我们定义了一个新的结构体数据类型,用于存放调用合约中的数据

  // hold its data in the calling contract.

  struct Data { mapping(uint => bool) flags; }

  // Note that the first parameter is of type "storage 注意第一个参数是 “存储引用”类型,这样仅仅是它的地址,而不是它的内容在调用中被传入 这是库函数的特点, 

  // reference" and thus only its storage address and not

  // its contents is passed as part of the call.  This is a

  // special feature of library functions.  It is idiomatic  若第一个参数用"self"调用时很笨的的,如果这个函数可以被对象的方法可见。

  // to call the first parameter 'self', if the function can

  // be seen as a method of that object.

  function insert(Data storage self, uint value)

      returns (bool)

  {

      if (self.flags[value])

          return false; // already there 已经在那里

      self.flags[value] = true;

      return true;

  }

  function remove(Data storage self, uint value)

      returns (bool)

  {

      if (!self.flags[value])

          return false; // not there 不在那里

      self.flags[value] = false;

      return true;

  }

  function contains(Data storage self, uint value)

      returns (bool)

  {

      return self.flags[value];

  }

}

contract C {

    Set.Data knownValues;

    function register(uint value) {

        // The library functions can be called without a  这个库函数没有特定的函数实例被调用,因为“instance”是当前的合约

        // specific instance of the library, since the

        // "instance" will be the current contract.

        if (!Set.insert(knownValues, value))

            throw;

    }

    // In this contract, we can also directly access knownValues.flags, if we want 在这个合约里,如果我们要的话,也可以直接访问 knownValues.flags

.*}

当然,你不必这样使用库--他们也可以事前不定义结构体数据类型,就可以使用。 没有任何存储引入参数,函数也可以执行。也可以在任何位置,有多个存储引用参数。

Set.contains, Set.insert and Set.remove都可编译到(CALLCODE)外部合约/库。如果你使用库,注意真正进行的外部函数调用,所以`msg.sender不再指向来源的sender了,而是指向了正在调用的合约。msg.value包含了调用库函数中发送的资金。

因为编译器不知道库将部署在哪里。这些地址不得不由linker填进最后的字节码(见使用命令行编译器如何使用命令行编译器链接)。如果不给编译器一个地址做参数,编译的十六进制码就会包含__Set __这样的占位符(Set是库的名字)。通过替换所有的40个字符的十六进制编码的库合约的地址,地址可以手动进行填充。

比较合约和库的限制:

  • 无状态变量
  • 不能继承或被继承

(这些可能在以后会被解除)

库的常见“坑”

msg.sender的值

msg.sender的值将是调用库函数的合约的值。

例如,如果A调用合约B,B内部调用库C。在库C库的函数调用里,msg.sender将是合约B的地址。

表达式LibraryName.functionName() 用CALLCODE完成外部函数调用, 它映射到一个真正的EVM调用,就像otherContract.functionName() 或者 this.functionName()。这种调用可以一级一级扩展调用深度(最多1024级),把msg.sender存储为当前的调用者,然后执行库合约的代码,而不是执行当前的合约存储。这种执行方式是发生在一个完全崭新的内存环境中,它的内存类型将被复制,并且不能绕过引用。

转移Ether

原则上使用LibraryName.functionName.value(x)()来转移Ether。但若使用CALLCODE,Ether会在当前合约里用完。

Using For

指令 using A for B;  可用于附加库函数(从库A)到任何类型(B)。这些函数将收到一个作为第一个参数的对象(像Python中self变量)。

using A for *;,是指函数从库A附加到任何类型。

在这两种情况下,所有的函数将被附加,(即使那些第一个参数的类型与对象的类型不匹配)。该被调用函数的入口类型将被检查,并进行函数重载解析。

using A for B; 指令在当前的范围里是有效的,作用范围限定在现在的合约里。但(出了当前范围)在全局范围里就被移除。因此,通过 including一个模块,其数据类型(包括库函数)都将是可用的,而不必添加额外的代码。

让我们用这种方式重写库中的set示例:

// This is the same code as before, just without comments

library Set {

  struct Data { mapping(uint => bool) flags; }

  function insert(Data storage self, uint value)

      returns (bool)

  {

      if (self.flags[value])

        return false; // already there

      self.flags[value] = true;

      return true;

  }

  function remove(Data storage self, uint value)

      returns (bool)

  {

      if (!self.flags[value])

          return false; // not there

      self.flags[value] = false;

      return true;

  }

  function contains(Data storage self, uint value)

      returns (bool)

  {

      return self.flags[value];

  }

}

contract C {

    using Set for Set.Data; // this is the crucial change

    Set.Data knownValues;

    function register(uint value) {

        // Here, all variables of type Set.Data have

        // corresponding member functions.

        // The following function call is identical to

        // Set.insert(knownValues, value)

        if (!knownValues.insert(value))

            throw;

    }

}

// This is the same code as before, just without comments   这个代码和之前的一样,仅仅是没有注释

library Set {

  struct Data { mapping(uint => bool) flags; }

  function insert(Data storage self, uint value)

      returns (bool)

  {

      if (self.flags[value])

        return false; // already there 已经在那里

      self.flags[value] = true;

      return true;

  }

  function remove(Data storage self, uint value)

      returns (bool)

  {

      if (!self.flags[value])

          return false; // not there 没有

      self.flags[value] = false;

      return true;

  }

  function contains(Data storage self, uint value)

      returns (bool)

  {

      return self.flags[value];

  }

}

contract C {

    using Set for Set.Data; // this is the crucial change   这个是关键的变化

    Set.Data knownValues;

    function register(uint value) {

        // Here, all variables of type Set.Data have   这里,所有Set.Data 的变量都有相应的成员函数

        // corresponding member functions.

        // The following function call is identical to  下面的函数调用和Set.insert(knownValues, value) 作用一样

        // Set.insert(knownValues, value)

        if (!knownValues.insert(value))

            throw;

    }

}

It is also possible to extend elementary types in that way:

这个也是一种扩展基本类型的(方式)

library Search {

    function indexOf(uint[] storage self, uint value) {

        for (uint i = 0; i < self.length; i++)

            if (self[i] == value) return i;

        return uint(-1);

    }}

contract C {

    using Search for uint[];

    uint[] data;

    function append(uint value) {

        data.push(value);

    }

    function replace(uint _old, uint _new) {

        // This performs the library function call   这样完成了库函数的调用

        uint index = data.find(_old);

        if (index == -1)

            data.push(_new);

        else

            data[index] = _new;

    }}

注意:所有的库函数调用都是调用实际的EVM。这意味着,如果你要使用内存或值类型,就必须执行一次拷贝操作,即使是self变量。拷贝没有完成的情况可能是存储引用变量已被使用。

Next  Previous

© Copyright 2015, Ethereum. Revision 37381072.

Built with Sphinx using a theme provided by Read the Docs.

如果你希望高效的学习以太坊DApp开发,可以访问汇智网提供的最热门在线互动教程:

其他更多内容也可以访问这个以太坊博客

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 智能合约
  • 创建合约
  • 可见性和访问限制符
  • 访问限制符函数
  • 函数修饰符
  • 常量
  • 回退函数
  • 事件
  • 底层日志的接口
  • 继承
  • 基本构造函数的参数
  • 多继承和线性化
  • 抽象契约
  • 库的常见“坑”
  • 转移Ether
  • Using For
相关产品与服务
云数据库 MongoDB
腾讯云数据库 MongoDB(TencentDB for MongoDB)是腾讯云基于全球广受欢迎的 MongoDB 打造的高性能 NoSQL 数据库,100%完全兼容 MongoDB 协议,支持跨文档事务,提供稳定丰富的监控管理,弹性可扩展、自动容灾,适用于文档型数据库场景,您无需自建灾备体系及控制管理系统。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档