Solidity里的智能合约是面向对象语言里的类。它们持久存放在状态变量和函数中,(在里面)能够经过solidity修改这些变量。在不一样的智能合约(实例)中调用一个函数(的过程),(实际上)是在EVM(Ether虚拟机)中完成一次调用,而且完成(一次)上下文切换,(此时)状态变量是不可访问的。html
合约能够从“外部”建立,也能够由Solidity合约创立。在建立合约时,它的构造函数(函具备与合约名称同名的函数)将被执行。python
web3.js,即 JavaScript API, 是这样作的:git
// 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,就没必要关心这个。github
若是是一个合约要创立另一个合约,被创立的合约的源码(二进制代码)要能被创立者知晓。这意味着:循环建立依赖就成为不可能的事情。web
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。json
external: 外部函数是合约接口的一部分,这意味着它们能够从其余合约调用, 也能够经过事务调用。外部函数f不能被内部调用(即 f()不执行,但this.f()执行)。外部函数,当他们接收大数组时,更有效。数组
**public:**公共函数是合约接口的一部分,能够经过内部调用或经过消息调用。对公共状态变量而言,会有的自动访问限制符的函数生成(见下文)。数据结构
**internal:**这些函数和状态变量只能内部访问(即在当前合约或由它派生的合约),而不使用(关键字)this 。app
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存储为当前的调用者,而后执行库合约的代码,而不是执行当前的合约存储。这种执行方式是发生在一个彻底崭新的内存环境中,它的内存类型将被复制,而且不能绕过引用。
原则上使用LibraryName.functionName.value(x)()来转移Ether。但若使用CALLCODE,Ether会在当前合约里用完。
指令 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开发,能够访问汇智网提供的最热门在线互动教程:
其余更多内容也能够访问这个以太坊博客。