合约
Solidity 中的合约类似于面向对象语言中的类。它们包含持久化数据在状态变量中,并且有可以修改这些变量的函数。调用另一个合约(实例)上的函数会执行一个 EVM 函数调用,从而切换上下文,使得调用合约中的状态变量不可访问。必须调用合约及其函数,才能发生任何操作。以太坊没有“cron”概念,无法在特定事件自动调用函数。
创建合约
合约可以通过以太坊交易从“外部”创建,也可以从 Solidity 合约内部创建。
如 Remix 等 IDE 可以通过 UI 元素使得创建过程更加顺畅。
一种在以太坊上程序化创建合约的方法是通过 JavaScript API web3.js。它有一个名为 web3.eth.Contract
的函数来简化合约创建。
当一个合约被创建时,其构造函数(使用 constructor
关键字声明的函数)会执行一次。
构造函数是可选的。只允许有一个构造函数,这意味着不支持重载。
构造函数执行完后,合约的最终代码会存储在区块链上。该代码包括所有公共和外部函数,以及所有可以通过函数调用访问的函数。已部署的代码不包括构造函数代码或仅在构造函数中调用的内部函数。
在内部,构造函数参数在合约代码之后以 ABI 编码的方式传递,但如果使用 web3.js,您不需要关心这个。
如果一个合约想要创建另一个合约,创建合约的源代码(和二进制)必须是已知的。这意味着不可能有循环创建依赖。
在 Remix 中打开
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract OwnedToken {
// `TokenCreator` 是在下面定义的合约类型。
// 只要不用于创建新合约,引用它是没问题的。
TokenCreator creator;
address owner;
bytes32 name;
// 这是构造函数,注册了
// creator 和分配的 name。
constructor(bytes32 name_) {
// 状态变量通过它们的名称来访问
// 而不是通过比如 `this.owner`。函数可以
// 直接访问或通过 `this.f` 访问,
// 但后者提供了对该函数的外部视图。
// 特别是在构造函数中,
// 不应该外部访问函数,
// 因为该函数尚不存在。
// 请参阅下一节了解详情。
owner = msg.sender;
// 我们从 `address` 显式类型转换到 `TokenCreator`
// 并假设调用合约的类型是 `TokenCreator`,
// 这里没有真正的方式验证这一点。
// 这并不会创建一个新合约。
creator = TokenCreator(msg.sender);
name = name_;
}
function changeName(bytes32 newName) public {
// 只有 creator 可以更改名称。
// 我们基于它的地址来比较合约,
// 地址可以通过显式转换为地址来获取。
if (msg.sender == address(creator))
name = newName;
}
function transfer(address newOwner) public {
// 只有当前所有者才能转移 token。
if (msg.sender != owner) return;
// 我们询问 creator 合约是否应该继续转移
// 通过使用下面定义的 `TokenCreator` 合约的函数。
// 如果调用失败(例如由于缺少 gas),
// 这里的执行也会失败。
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name)
public
returns (OwnedToken tokenAddress)
{
// 创建一个新的 `Token` 合约并返回其地址。
// 从 JavaScript 端来看,该函数的返回类型
// 是 `address`,因为这是 ABI 中最接近的类型。
return new OwnedToken(name);
}
function changeName(OwnedToken tokenAddress, bytes32 name) public {
// 同样,`tokenAddress` 的外部类型
// 仅仅是 `address`。
tokenAddress.changeName(name);
}
// 执行检查以确定是否应该将 token 转移到
// `OwnedToken` 合约
function isTokenTransferOK(address currentOwner, address newOwner)
public
pure
returns (bool ok)
{
// 检查一个任意