web3j教程:以太坊智能合约

开发人员可以选择三种语言来编写智能合约:

  • 1.solidity,以太坊的旗舰语言,也是开发智能合约最流行的语言。
  • 2.Serpent,一个用于编写智能合约的Python语言。
  • 3.类LISP语言(LLL),低级语言,Serpent提供了LLL的超集。使用LLL的信息不多,这个博客/var/log/syrinx和github相关的库lll-resurrected GitHub不错,可以参考参考。

为了将智能合约部署到以太坊区块链上,必须先将其编译成字节码的格式,然后将其作为交易的一部分发送。web3j可以帮你做这些并且将它与solidity智能合约封装起来。为了了解幕后发生的事情,你可以参考智能合约创建中的细节。

鉴于solidity是编写智能合约的首选语言,它也是web3j支持的语言,随后的所有示例都会用到它。

从solidity语言开始

solidity的概述不是本文要讨论的重点,但是我们还是要推荐一些资料给大家:

编译solidity源代码

通过solc编译器编译solidity代码到字节码。你可以参考solidity安装文档在本地安装编译器。

编写solidity代码运行:

1
$ solc <contract>.sol --bin --abi --optimize -o <output-dir>/

binABI编译器参数都需要充分利用web3j中的智能合约。

  • bin,输出包含十六进制编码的solidity二进制文件以提供交易请求。
  • ABI,输出一个solidity的应用程序二进制接口(ABI)文件,它详细描述了所有可公开访问的合约方法及其相关参数。这些细节和合约地址对于智能合约的交互是至关重要的。ABI文件也用于封装solidity的智能合约。

这里也有一个gas论证,用来提供智能合约所需gas的估计值,并用它的方法进行交易。

你可以通过solidity的浏览器项目Remix在浏览器中编写和编译solidity代码。使用solidity浏览器项目Remix IDE对于较小的智能合约来说是很好的,但是你遇到与较大的智能合约相关的问题。

还可以通过以太坊客户端(如geth和Parity)编译solidity代码,web3j中也支持使用JSON-RPC方法 eth_compileSolidity。然而,必须在客户端上安装solidity编译器,这样它才能正常工作。

还有更多的选择,请参阅官网文档中的相关章节

部署智能合约及与智能合约交互

如果你想避免使用智能合约的底层实现细节,web3j提供solidity的智能合约的封装包,使你能够通过web3j生成的包装对象直接与智能合约的所有方法进行交互。

另外,如果你希望发送常规交易或希望在与智能合约的交互中有更多的控制权,请参阅web3j教程:java使用web3j开发以太坊智能合约交易,这里讲到了创建智能合约,与智能合约进行交易,查询智能合约的状态等内容。

智能合约示例demo

web3j在项目目录中提供了许多智能合约示例demo:

codegen/src/test/resources/solidity

它还提供了集成测试,用于演示在集成测试模块中部署和使用这些智能合约: integration-tests/src/test/java/org/web3j/protocol/scenarios

smart_contract

EIP-20 以太坊智能合约通证(token)标准

有一个以太坊标准EIP-20,它是作为一个以太坊改进方案EIP开始的,它定义了提供通证的智能合约应该执行的标准功能。

EIP-20标准提供函数定义,但不提供示例实现。然而,在codegen/src/test/resources/solidity/contracts中提供了一个实现,这已经从ConsenSys’ Tokens project获得。

开源的Zepplin还在github上提供了一个示例实现

已经编写了两个集成测试来充分展示这个智能合约的通证功能。

HumanStandardTokenGeneratedIT使用生成的 HumanStandardTokenGenerated智能合约封装包来实现这一功能。

或者,如果你不希望使用封装好的智能合约标准包,并希望直接使用JSON-RPC来做调用,请参阅HumanStandardTokenIT

智能合约封装包(Solidity smart contract wrappers)

web3j支持java从ABI文件中自动生成智能合约函数封装包。

web3j还可以使用命令行工具来生成智能合约函数封装包:

1
$ web3j solidity generate [--javaTypes|--solidityTypes] /path/to/<smart-contract>.bin /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name

在web3j的3.x之前的版本中,生成的智能合约封装包使用原生Solidity类型。从web3j 3.x开始,默认会采用Java类型。可以使用–solidityTypes命令行参数创建solidity类型。

还可以通过直接调用Java类来生成封装包:

1
org.web3j.codegen.SolidityFunctionWrapperGenerator /path/to/<smart-contract>.bin /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name

其中bin和abi是根据本文中编译solidity源代码章节的方法获得的。

在应用程序二进制接口ABI的官方文档中详细说明了原生Java到solidity类型如何转换。

智能合约封装包支持所有与智能合约一起工作的通用操作:

  • 构建与部署
  • 调用交易和事件
  • 调用参数方法
  • 确定合约有效性

任何需要底层JSON-RPC调用的方法调用都会有一个返回以避免阻塞。

web3还支持通过命令行工具直接从Tunffle的合约模式生成Java智能合约功能封装包。

1
$ web3j truffle generate [--javaTypes|--solidityTypes] /path/to/<truffle-smart-contract-output>.json -o /path/to/src/main/java -p com.your.organisation.name

这也可以通过调用Java类来调用:

1
org.web3j.codegen.TruffleJsonFunctionWrapperGenerator /path/to/<truffle-smart-contract-output>.json -o /path/to/src/main/java -p com.your.organisation.name

以这种方式生成的”增强型”的封装包会暴露合约的每个网络部署地址。这些地址来自于发布Truffle部署时的封装包。

构建与部署智能合约(Construction and deployment)

智能合约的构建和部署发生在deploy方法中:

1
2
3
4
YourSmartContract contract = YourSmartContract.deploy(
<web3j>, <credentials>, GAS_PRICE, GAS_LIMIT,
[<initialValue>,]
<param1>, ..., <paramN>).send();

这个方法将使用提供的凭据和构造函数参数值在以太坊区块链上创建智能合约的新实例。

如果你的智能合约在构造上接受以太币,则只需要初始化参数值<initialValue>。这就要求在合约中出现支付payable

它会返回一个新的智能合约封装实例,它包含智能合约的底层地址。如果希望用现有的智能合约构造智能合约封装的实例,只需传递地址:

1
2
YourSmartContract contract = YourSmartContract.load(
"0x<address>|<ensName>", web3j, credentials, GAS_PRICE, GAS_LIMIT);

智能合约有效性(Contract validity)

使用此方法,可能希望确定所加载的合约地址是你所期望的智能合约。为此,你可以使用isValid合约方法,只有在合约地址中部署的字节码与智能合约封装包中的字节码匹配时才会返回true。

1
2
contract.isValid();  // returns false if the contract bytecode does not match what's deployed
// at the provided address

交易管理器(Transaction Managers)

web3j提供了一个交易管理器TransactionManager来控制你连接到以太坊客户端的方式。默认机制使用web3j的RawTransactionManager,它与以太坊钱包文件一起工作,在提交到网络之前离线地签署交易。

但是,你可能希望修改交易管理,也可以将其传递给智能合约部署和构建方法deploy,而不是凭据对象,即:

1
2
3
YourSmartContract contract = YourSmartContract.deploy(
<web3j>, <transactionManager>, GAS_PRICE, GAS_LIMIT,
<param1>, ..., <paramN>).send();

除了RawTransactionManager之外,web3j还提供了一个客户端交易管理器ClientTransactionManager,它将你的交易签署的责任传递给你正在连接的以太坊客户端。

还有一个ReadonlyTransactionManager,用于只从智能合约中查询数据,而不与它进行交易。

在交易中指定链ID(EIP-155)

RawTransactionManager采用一个可选的链表参数来指定在EIP-155上使用的链ID。这防止了一个链被重新广播到另一个链上的交易,例如从RopstenMainnet

1
2
TransactionManager transactionManager = new RawTransactionManager(
web3j, credentials, ChainId.MAIN_NET);

为了避免更改配置或代码来指定你正在使用的链,web3j默认是不指定交易上的链ID,以简化与库间的交互。然而,以太坊社区的建议是使用链ID。

你可以通过以下请求获得你的以太坊客户端连接到的网络的链ID:

1
web3j.netVersion().send().getNetVersion();

交易收据处理器(Transaction Receipt Processors)

默认情况下,当web3j向以太坊客户端提交新的交易时,web3j将继续轮询客户端直到收据到TransactionReceipt,指示交易已被添加提交到区块链上。如果你用web3j异步发送多个交易,则这会导致多个线程同时轮询客户端。

为了减少这种轮询开销,web3j提供可配置的交易收据处理器TransactionReceiptProcessors

在web3j中提供了多个处理器:

  • PollingTransactionReceiptProcessor轮询交易收据处理器是web3j中使用的默认处理器,它定期为每个未决交易的交易收据进行轮询。
  • QueuingTransactionReceiptProcessor排队交易收据处理器具有所有挂起的交易的内部队列。它包含一个定期运行以查询交易收据是否可用的worker。如果找到了收据,则调用客户端的回调。
  • NoOpProcessor向客户端只提供交易hash,提供了一个EmptyTransactionReceipt。这是为不希望web3j对交易收据进行任何轮询的客户端使用的。

注意:在排队交易收据处理器的初始响应中也提供了EmptyTransactionReceipt。这允许调用方对提交到网络的交易拥有交易哈希。

如果你不希望使用默认处理器PollingTransactionReceiptProcessor,则可以指定交易收据处理器代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
TransactionReceiptProcessor transactionReceiptProcessor =
new QueuingTransactionReceiptProcessor(web3j, new Callback() {
@Override
public void accept(TransactionReceipt transactionReceipt) {
// process transactionReceipt
}

@Override
public void exception(Exception exception) {
// handle exception
}
TransactionManager transactionManager = new RawTransactionManager(
web3j, credentials, ChainId.MAIN_NET, transactionReceiptProcessor);

如果你需要进一步的信息,FastRawTransactionManagerIT演示轮询和排队方法。

调用交易和事件(Invoking transactions and events)

所有交易的智能合约方法以相同的参数值命名为它们的solidity方法。交易调用不返回任何值,同样不需要考虑方法指定的返回类型。因此,对于所有交易的方法,只是返回与交易关联的交易收据。

1
2
3
TransactionReceipt transactionReceipt = contract.someMethod(
<param1>,
...).send();

交易收据是十分有用的有两个主要原因:

  • 它提供了交易驻留的挖掘块的详细信息。
  • 被调用的solidity事件将被记录为交易的一部分,然后可以被提取。

在智能合约中定义的任何事件都将用一个名为process<Event Name>Event方法在智能合约封装包中进行表示,该事件采用交易收据,并从中提取索引和非索引事件参数,这些参数在EventValues实例中被解码返回:

1
EventValues eventValues = contract.processSomeEvent(transactionReceipt);

或者,你可以使用可观察的过滤器Observable filter,而不是监听与智能合约相关联的事件:

1
2
contract.someEventObservable(startBlock, endBlock).
.subscribe(event -> ...);

有关使用可观察过滤器的详细信息,请参阅过滤器和事件

请记住,对于任何索引数组、字节和字符串solidity参数类型,它们的Keccak-256 hash值将被返回,请参阅事件文档以获取更多信息。

调用常量方法(Calling constant methods)

常量方法是在智能合约中读取值的方法,而不改变智能合约的状态。这些方法与它们生成的智能合约相同的方法签名都是可用的:

1
Type result = contract.someMethod(<param1>, ...).send();

动态gas价格与限价

在使用智能合约时,你可能需要根据调用函数指定不同的gas价格和最大值。你可以通过为智能合约封装包创建自己的ContractGasProvider来实现这一点。

每一个生成的封装包都包含作为常量的所有智能合约方法名称,这有助于通过switch来进行编译时间匹配。

例如,使用Greeter合约:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Greeter greeter = new Greeter(...);
greeter.setGasProvider(new DefaultGasProvider() {
@Override
public BigInteger getGasPrice(String contractFunc) {
switch (contractFunc) {
case Greeter.FUNC_GREET: return BigInteger.valueOf(22_000_000_000L);
case Greeter.FUNC_KILL: return BigInteger.valueOf(44_000_000_000L);
default: throw new NotImplementedException();
}
}

@Override
public BigInteger getGasLimit(String contractFunc) {
switch (contractFunc) {
case Greeter.FUNC_GREET: return BigInteger.valueOf(4_300_000);
case Greeter.FUNC_KILL: return BigInteger.valueOf(5_300_000);
default: throw new NotImplementedException();
}
}
});

web3j实例(demo示例)

请看本文中的 EIP-20以太坊通证标准智能合约 章节。

  • web3j教程,主要是针对java和android程序员进行区块链以太坊开发的web3j开发详解。
  • 以太坊教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。

汇智网原创翻译,转载请标明出处。原文