合约
Solidity合约与面向对象语言中的类相似。它们包含可以修改这些变量的状态变量和函数中的持久数据。在不同的合约(实例)上调用函数将执行EVM函数调用,从而切换上下文以使状态变量不可访问。
创建合约
合约可以从“外部”或从Solidity合约创建。创建合约时,其构造函数(与合约名称相同的函数)将执行一次。
构造函数是可选的。只允许一个构造函数,这意味着不支持重载。
从web3.js,即JavaScript API,这是按如下方式完成的:
// Need to specify some source including contract name for the data param below
var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";
// 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(source);
MyContract = web3.eth.contract(MyContract_.CONTRACT_NAME.info.abiDefinition);
// deploy new contract
var contractInstance = MyContract.new(
10,
11,
{from: myAccount, gas: 1000000}
);
在内部,构造函数参数是在合约本身的代码之后传递的,但如果使用web3.js,则不必关心这一点。
如果合约要创建另一个合约,则创建者必须知道所创建合约的源代码(和二进制文件)。这意味着循环创建依赖是不可能的。
pragma solidity ^0.4.0;
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) {
// 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) {
// 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) {
// 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 (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
}
}
可见标识的及getter方法
由于Solidity知道两种函数调用(不产生实际EVM调用的内部函数(也称为“消息调用”)和外部函数调用),函数和状态变量有四种类型的可见性。
函数可以被指定为external
, public
, internal
和 private
,默认为public
。对于状态变量,external
是不被允许的,默认是internal
。
external
:
external
函数是合约接口的一部分,这意味着可以从其他合约和交易中调用它们。external
函数f不能在内部调用(即f()不起作用,但this.f()起作用)。external
函数在接收大量数据时有时更高效。
public
:
public
函数是合约接口的一部分,可以在内部或通过消息调用。对于公共状态变量,会生成一个自动填充getter方法(见下文)。
internal
:
这些函数和状态变量只能在内部进行访问(即从当前合约或从中得出的合同),而不使用this
。
private
:
private
函数和状态变量仅对它们定义的合约而不是衍生合约中可见。
注意:所有外部观察者都可以看到合约内的所有内容。使用private
修饰只会阻止其他合约访问和修改信息,但在区块链之外,整个世界仍然可以看到它。
可见性说明符在状态变量的类型之后以及函数的参数列表和返回参数列表之间给出。
pragma solidity ^0.4.0;
contract C {
function f(uint a) private returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
在下面的例子中,D
可以调用c.getData()
来检索状态存储中的数据值,但不能调用f
。合约E
来自C
,因此可以调用compute
。
// This will not compile
pragma solidity ^0.4.0;
contract C {
uint private data;
function f(uint a) private returns(uint b) { return a + 1; }
function setData(uint a) { 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() {
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() {
C c = new C();
uint val = compute(3, 5); // acces to internal member (from derivated to parent contract)
}
}
Getter函数
编译器自动为所有公共状态变量创建getter函数。对于下面给出的合约,编译器将生成一个名为data
的函数,该函数不接受任何参数并返回uint
,状态变量数据的data
。状态变量的初始化可以在声明中完成。
pragma solidity ^0.4.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() {
uint local = c.data();
}
}
getter
函数具有外部可见性。如果符号是在内部访问的(即没有this
),则它被评估为一个状态变量。如果它被外部访问(即用this
),它被作为一个函数来评估。
pragma solidity ^0.4.0;
contract C {
uint public data;
function x() {
data = 3; // internal access
uint val = this.data(); // external access
}
}
下一个例子有点复杂:
pragma solidity ^0.4.0;
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;
}
请注意,结构中的映射被省略,因为没有提供映射关键的好方法。