智能合约简介

YOUChain 的智能合约使用 Solidity 语言开发。你可以参考solidity安装文档在本地安装编译器。
创建一个简单的加减算发的智能合约,如下:

pragma solidity ^0.4.13;

contract arithmetic {

    function arithmetic_add(uint a, uint b) public pure returns(uint d) {
        return a + b;
    }

    function arithmetic_subtract(uint a, uint b) public pure returns(uint d) {
        return a - b;
    }
}

编译solidity合约代码

1.可以通过经过 YOUChain 优化的命令行工具 youchain-solc 编译 Solidity 代码到字节码。

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

2.也可以通过YOUChain 智能合约编译器在浏览器中编写和编译 Solidity 代码在编译器右侧获得 abi 和 bin 文件。

部署智能合约1(根据bin文件数据)

打开 .bin 文件,获取智能合约的二进制数据。然后调用交易,部署该智能合约。

String contractBinary = "<contract>.bin文件里的内容"
String data = "0x" + contractBinary;
RawTransaction rawTransaction = RawTransaction.createContractTransaction(nonce, gasPrice, gasLimit, initialValue, data);
byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credential);
String signedData = Numeric.toHexString(signMessage);
YOUSendTransaction youSendTransaction = youChain.youSendRawTransaction(signedData).send();
String transactionHash = youSendTransaction.getTransactionHash();

注意:如果智能合约中有构造函数和构造参数,创建智能合约是需要将构造函数字段值进行编码后附加到编译的智能合约代码中:
方式如下:

String encodedConstructor = FunctionEncoder.encodeConstructor(Arrays.asList(new Type(value), ...));
String data = "0x" + contractBinary + encodedConstructor;
RawTransaction rawTransaction = RawTransaction.createContractTransaction(nonce, gasPrice, gasLimit, initialValue, data);

其中 initialValue 设置为非零数值,表示该合约支持接收 YOU,同时需要智能合约代码定义时允许支付,即出现payable关键字。如果initialValue设置为零则不需要。

等交易获得确认后,查询交易凭证,取出合约地址。

TransactionReceipt transactionReceipt = youChain.youGetTransactionReceipt(youSendTransaction.getTransactionHash()).send().getResult();
String contractAddress = transactionReceipt.getContractAddress();

部署智能合约2(根据生成的智能合约封装类)

youchain.java 支持根据合约编译结果生成智能合约封装类。

1.可以通过命令行工具生成封装类。

YOUChainJ solidity generate --solidityTypes /path/<contract>.bin /path/<contract>.abi -o /path/to -p com.your.package

2.也可以通过调用 SolidityFunctionWrapperGenerator 这个类来生成封装类,如下:

java cc.youchain.codegen.SolidityFunctionWrapperGenerator /path/<contract>.bin /path/<contract>.abi -o /path/to -p com.your.package

bin 和 abi 文件是通过 youchain-solcjs 命令行工具编译 Solidity 源代码获得的。

3.另外还通过 YOUBox 工具 编译出来的 JSON 文件来生成合约封装类

youbox generate [--javaTypes|--solidityTypes] <input youbox json file>.json -p com.your.package -o /output/path

备注:YOUBox 生成的 JSON 文件是在 YOUBox 项目的 build 文件夹下

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

  • 构建与部署
  • 调用交易和事件
  • 调用参数方法
  • 确定合约有效性 任何需要底层 JSON-RPC 调用的方法调用都会有一个返回以避免阻塞。
    接下来可以使用封装类进行部署智能合约:
    YourContract contract = YourContract.deploy(youchain, credentials, gasPrice, gasLimit, 
                                                  <initialValue>, <param1>, ..., <paramN>).send();
    String contractAddress = contract.getContractAddress();
    
    其中 initialValue 设置为非零数值,表示该合约支持接收YOU,同时需要智能合约代码定义时允许支付,即出现payable关键字。如果initialValue设置为零则不需要。 param1, ..., paramN 表示构造参数,如果合约没有构造参数,这里可以不写。

验证智能合约有效性

首先需要加载智能合约,然后调用contract的isValid()来进行验证,isValid()可以验证合约是否有效,只有在合约地址中部署的字节码与智能合约封装包中的字节码匹配时才会返回true
你可以使用前面生成的智能合约封装类来验证:

ContractGasProvider gasProvider = new DefaultGasProvider();
YourContract contract = YourContract.load(contractAddress, youchain, credentials, gasProvider);
boolean contractIsValid = contract.isValid(); // 合约是否有效

也可以通过实现 Contract 类来进行验证:

class DemoContract extends Contract {
    protected DemoContract(String contractBinary, String contractAddress,
                               YOUChain youChain, TransactionManager transactionManager,
                               ContractGasProvider gasProvider) {
        super(contractBinary, contractAddress, youChain, transactionManager, gasProvider);
    }
    protected DemoContract(String contractBinary, String contractAddress, YOUChain youChain,
                               Credentials credentials, ContractGasProvider gasProvider) {
        super(contractBinary, contractAddress, youChain, credentials, gasProvider);
    }
}

ContractGasProvider gasProvider = new DefaultGasProvider();
DemoContract contract = new DemoContract(contractBinary, contractAddress, youChain, credential, gasProvider);
boolean contractIsValid = contract.isValid(); // 合约是否有效

其中,contractBinary 是 Solidity 合约编译后bin文件的二进制合约内容。

与智能合约交易(自编码方式)

假如合约(.sol文件)中有一个转账接口:

function transfer(address _to, uint256 _amount) public returns (bool success) {
    // ...
}
  1. 方式一:(离线签名)发送转账交易,会消耗gas费
    通过如下方式发起转账交易:
    Function function = new Function("transfer",  // function we're calling
         Arrays.asList(new Address(toAddress), new Uint256(BigInteger.valueOf(30))),  // Parameters to pass as Solidity Types
         Arrays.asList(new TypeReference<Bool>() {
         }));
    String encodedFunction = FunctionEncoder.encode(function);
    RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, contractAddress, encodedFunction);
    // 签名交易数据
    byte[] signMessage = TransactionEncoder.signMessage(rawTransaction, credential);
    String signedTransactionData = Numeric.toHexString(signMessage);
    String transactionHash = youChain.youSendRawTransaction(signedTransactionData).send().getTransactionHash();
    
  2. 方式二:发送转账交易,会消耗 gas 费
    Function function = new Function("transfer",  // function we're calling
         Arrays.asList(new Address(toAddress), new Uint256(BigInteger.valueOf(30))),  // Parameters to pass as Solidity Types
         Arrays.asList(new TypeReference<Bool>() {
         }));
    String encodedFunction = FunctionEncoder.encode(function);
    Transaction transaction = Transaction.createFunctionCallTransaction(fromAddress, gasPrice, gasLimit, contractAddress, value, encodedFunction);
    YOUSendTransaction youSendTransaction = youchain.youSendTransaction(transaction).sendAsync().get();
    String transactionHash = youSendTransaction.getTransactionHash();
    
    其中value:在智能合约中你希望存放的YOU数量(如果智能合约接受YOU的话)

查询智能合约状态

使用 youCall 来查询智能合约上的值,此函数没有关联交易成本,它不改变任何智能合约方法的状态,它只返回它们的值,所以不会消耗gas费

Function function = new Function("arithmetic_add",  // function we're calling
        Arrays.asList(new Uint(BigInteger.valueOf(20)), new Uint(BigInteger.valueOf(10))),  // Parameters to pass as Solidity Types
        Arrays.asList(new TypeReference<Uint>() {}));
String encodedFunction = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createYOUCallTransaction(fromAddress, contractAddress, encodedFunction);
YOUCall youCall = youChain.youCall(transaction, DefaultBlockParameterName.LATEST).sendAsync().get();
List<Type> someTypes = FunctionReturnDecoder.decode(response.getValue(), function.getOutputParameters());

注意:如果一个无效的函数调用被执行,或者得到一个空null返回结果时,返回值将是一个Collections.emptyList实例。

与智能合约交易(使用智能合约封装类)

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

TransactionReceipt transactionReceipt = contract.someMethod(param1, ...).send()

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

  • 它提供了交易驻留的挖掘块的详细信息。
  • 被调用的solidity事件将被记录为交易的一部分,然后可以被提取。 在智能合约中定义的任何事件都将用一个名为processEvent方法在智能合约封装包中进行表示,该事件采用交易收据,并从中提取索引和非索引事件参数,这些参数在EventValues实例中被解码返回:
    EventValues eventValues = contract.processSomeEvent(transactionReceipt);
    
    或者,你可以使用可观察的过滤器 Flowable filter,而不是监听与智能合约相关联的事件:
    contract.someEventFlowable(startBlock, endBlock).subscribe(event -> ...);
    

动态gas价格与限价

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

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

例如,使用Greeter合约:

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

智能合约事件监听

假设合约中有个 Modified 事件,调用 newGreeting 方法触发此事件。

// 事件
event Modified(
            string indexed oldGreetingIdx, string indexed newGreetingIdx,
            string oldGreeting, string newGreeting);

//方法
function newGreeting(string _greeting) public {
    emit Modified(greeting, _greeting, greeting, _greeting);
    greeting = _greeting;
}

监听事件代码如下:

Function function = new Function("newGreeting",  // function we're calling
        Arrays.<Type>asList(new Utf8String("Hello")),  // Parameters to pass as Solidity Types
        Arrays.asList());
String encodedFunction = FunctionEncoder.encode(function);
Transaction transaction = Transaction.createYOUCallTransaction(fromAddress, contractAddress, encodedFunction);
YOUCall youCall = youChain.youCall(transaction, DefaultBlockParameterName.LATEST).send();

Event event = new Event("Modified",
        Arrays.asList(
                new TypeReference<Utf8String>() {
                },
                new TypeReference<Utf8String>() {
                },
                new TypeReference<Utf8String>() {
                },
                new TypeReference<Utf8String>() {
                }));
YOUFilter filter = new YOUFilter(DefaultBlockParameterName.EARLIEST,
        DefaultBlockParameterName.LATEST, contractAddress);
filter.addSingleTopic(EventEncoder.encode(event));
Flowable<Log> flowable = youChain.youLogFlowable(filter);
flowable.subscribe(log -> {
    logger.info("log={}", log);
}, error -> {
    logger.info("error={}", error);
});

ABI 类型映射关系

Java 原始类型和 Solidity ABI 类型的映射关系如下:

Java Solidity
boolean bool
BigInteger uint/int
byte[] bytes
String string/address
List dynamic/static array