Version 1.0.0
English / 中文
这个章节介绍如何通过Java SDK使用智能合约。
Note:目前java-sdk支持neo智能合约部署和调用,暂不支持WASM合约,NEO和WASM合约部署操作一样,调用略有不同,见下面详解:
通过SmartX编译智能合约,可以在SmartX上直接部署合约,也可以通过java sdk部署合约。
InputStream is = new FileInputStream("/Users/sss/dev/ontologytest/IdContract/IdContract.avm");
byte[] bys = new byte[is.available()];
is.read(bys);
is.close();
code = Helper.toHexString(bys);
ontSdk.setCodeAddress(Address.AddressFromVmCode(code).toHexString());
//部署合约
Transaction tx = ontSdk.vm().makeDeployCodeTransaction(code, true, "name",
"v1.0", "author", "email", "desp", account.getAddressU160().toBase58(),ontSdk.DEFAULT_DEPLOY_GAS_LIMIT,500);
String txHex = Helper.toHexString(tx.toArray());
ontSdk.getConnect().sendRawTransaction(txHex);
//等待出块
Thread.sleep(6000);
DeployCodeTransaction t = (DeployCodeTransaction) ontSdk.getConnect().getTransaction(txHex);
makeDeployCodeTransaction
public DeployCode makeDeployCodeTransaction(String codeStr, boolean needStorage, String name, String codeVersion, String author, String email, String desp,String payer,long gaslimit,long gasprice)
参数 | 字段 | 类型 | 描述 | 说明 |
---|---|---|---|---|
输入参数 | codeHexStr | String | 合约code十六进制字符串 | 必选 |
needStorage | Boolean | 是否需要存储 | 必选 | |
name | String | 名字 | 必选 | |
codeVersion | String | 版本 | 必选 | |
author | String | 作者 | 必选 | |
String | emal | 必选 | ||
desp | String | 描述信息 | 必选 | |
VmType | byte | 虚拟机类型 | 必选 | |
payer | String | 支付交易费用的账户地址 | 必选 | |
gaslimit | long | gaslimit | 必选 | |
gasprice | long | gas价格 | 必选 | |
输出参数 | tx | Transaction | 交易实例 |
- 基本流程:
- 构造调用智能合约调用参数;
- 构造交易;
- 交易签名(预执行不需要签名);
- 发送交易。
- 示例
List paramList = new ArrayList<>();
paramList.add("testHello".getBytes());
List args = new ArrayList();
args.add(true);
args.add(100);
args.add("test".getBytes());
args.add("test");
args.add(account.getAddressU160().toArray());
paramList.add(args);
byte[] params = BuildParams.createCodeParamsScript(paramList);
String result = invokeContract(params, account, 20000, 500,true);
System.out.println(result);
public static String invokeContract(byte[] params, Account payerAcct, long gaslimit, long gasprice, boolean preExec) throws Exception{
if(payerAcct == null){
throw new SDKException("params should not be null");
}
if(gaslimit < 0 || gasprice< 0){
throw new SDKException("gaslimit or gasprice should not be less than 0");
}
Transaction tx = ontSdk.vm().makeInvokeCodeTransaction(Helper.reverse(contractAddress),null,params,payerAcct.getAddressU160().toBase58(),gaslimit,gasprice);
ontSdk.addSign(tx, payerAcct);
Object result = null;
if(preExec) {
result = ontSdk.getConnect().sendRawTransactionPreExec(tx.toHexString());
}else {
result = ontSdk.getConnect().sendRawTransaction(tx.toHexString());
return tx.hash().toString();
}
return result.toString();
}
-
基本流程:
- 构造调用合约中的方法需要的参数;
- 构造交易;
- 交易签名(如果是预执行不需要签名);
- 发送交易。
-
示例:
//设置要调用的合约地址codeAddress
ontSdk.getSmartcodeTx().setCodeAddress(codeAddress);
String funcName = "add";
//构造合约函数需要的参数
String params = ontSdk.vm().buildWasmContractJsonParam(new Object[]{20,30});
//指定虚拟机类型构造交易
Transaction tx = ontSdk.vm().makeInvokeCodeTransaction(ontSdk.getSmartcodeTx().getCodeAddress(),funcName,params.getBytes(),VmType.WASMVM.value(),payer,gas);
//发送交易
ontSdk.getConnect().sendRawTransaction(tx.toHexString());
合约中的方法
public static bool Transfer(byte[] from, byte[] to, object[] param)
{
StorageContext context = Storage.CurrentContext;
if (from.Length != 20 || to.Length != 20) return false;
for (int i = 0; i < param.Length; i++)
{
TransferPair transfer = (TransferPair)param[i];
byte[] hash = GetContractHash(transfer.Key);
if (hash.Length != 20 || transfer.Amount < 0) throw new Exception();
if (!TransferNEP5(from, to, hash, transfer.Amount)) throw new Exception();
}
return true;
}
struct TransferPair
{
public string Key;
public ulong Amount;
}
Java-SDK 调用Transfer函数的方法
分析:合约中的Transfer方法需要三个参数,前两个参数都是字节数组类型的参数,最后一个参数是对象数组,数组中的每个元素的结构可以通过TransferPair知道各个属性数据类型。
String functionName = "Transfer";
//构造Transfer方法需要的param 数组
List list = new ArrayList();
List list2 = new ArrayList();
list2.add("Atoken");
list2.add(100);
list.add(list2);
List list3 = new ArrayList();
list3.add("Btoken");
list3.add(100);
list.add(list3);
//设置函数需要的参数
func.setParamsValue(account999.getAddressU160().toArray(),Address.decodeBase58("AacHGsQVbTtbvSWkqZfvdKePLS6K659dgp").toArray(),list);
String txhash = ontSdk.neovm().sendTransaction(Helper.reverse("44f1f4ee6940b4f162d857411842f2d533892084"),acct,acct,20000,500,func,false);
Thread.sleep(6000);
System.out.println(ontSdk.getConnect().getSmartCodeEvent(tx.hash().toHexString()));
如果需要监控推送结果,可以了解下面章节。
创建websocket线程,解析推送结果。
//lock 全局变量,同步锁
public static Object lock = new Object();
//获得ont实例
String ip = "http://127.0.0.1";
String wsUrl = ip + ":" + "20335";
OntSdk wm = OntSdk.getInstance();
wm.setWesocket(wsUrl, lock);
wm.setDefaultConnect(wm.getWebSocket());
wm.openWalletFile("OntAssetDemo.json");
//false 表示不打印回调函数信息
ontSdk.getWebSocket().startWebsocketThread(false);
Thread thread = new Thread(
new Runnable() {
@Override
public void run() {
waitResult(lock);
}
});
thread.start();
//将MsgQueue中的数据取出打印
public static void waitResult(Object lock) {
try {
synchronized (lock) {
while (true) {
lock.wait();
for (String e : MsgQueue.getResultSet()) {
System.out.println("RECV: " + e);
Result rt = JSON.parseObject(e, Result.class);
//TODO
MsgQueue.removeResult(e);
if (rt.Action.equals("getblockbyheight")) {
Block bb = Serializable.from(Helper.hexToBytes((String) rt.Result), Block.class);
//System.out.println(bb.json());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
for (;;){
Map map = new HashMap();
if(i >0) {
map.put("SubscribeEvent", true);
map.put("SubscribeRawBlock", false);
}else{
map.put("SubscribeJsonBlock", false);
map.put("SubscribeRawBlock", true);
}
//System.out.println(map);
ontSdk.getWebSocket().setReqId(i);
ontSdk.getWebSocket().sendSubscribe(map);
Thread.sleep(6000);
}
以调用存证合约的put函数为例,
//存证合约abi.json文件部分内容如下
{
"hash":"0x27f5ae9dd51499e7ac4fe6a5cc44526aff909669",
"entrypoint":"Main",
"functions":
[
],
"events":
[
{
"name":"putRecord",
"parameters":
[
{
"name":"arg1",
"type":"String"
},
{
"name":"arg2",
"type":"ByteArray"
},
{
"name":"arg3",
"type":"ByteArray"
}
],
"returntype":"Void"
}
]
}
当调用put函数保存数据时,触发putRecord事件,websocket 推送的结果是{"putRecord", "arg1", "arg2", "arg3"}的十六进制字符串
例子如下:
RECV:
{
"Action": "Log",
"Desc": "SUCCESS",
"Error": 0,
"Result": {
"Message": "Put",
"TxHash": "8cb32f3a1817d88d8562fdc0097a0f9aa75a926625c6644dfc5417273ca7ed71",
"ContractAddress": "80f6bff7645a84298a1a52aa3745f84dba6615cf"
},
"Version": "1.0.0"
}
RECV: {
"Action": "Notify",
"Desc": "SUCCESS",
"Error": 0,
"Result": [{
"States": ["7075745265636f7264", "507574", "6b6579", "7b2244617461223a7b22416c6772697468656d223a22534d32222c2248617368223a22222c2254657874223a2276616c75652d7465737431222c225369676e6174757265223a22227d2c2243416b6579223a22222c225365714e6f223a22222c2254696d657374616d70223a307d"],
"TxHash": "8cb32f3a1817d88d8562fdc0097a0f9aa75a926625c6644dfc5417273ca7ed71",
"ContractAddress": "80f6bff7645a84298a1a52aa3745f84dba6615cf"
}],
"Version": "1.0.0"
}
- contractAddress是什么
contractAddress是智能合约的唯一标识。
- 如何获得contractAddress ?
InputStream is = new FileInputStream("IdContract.avm");
byte[] bys = new byte[is.available()];
is.read(bys);
is.close();
code = Helper.toHexString(bys);
System.out.println("Code:" + Helper.toHexString(bys));
System.out.println("CodeAddress:" + Address.AddressFromVmCode(code).toHexString());
Note: 在获得codeAddress的时候,需要设置该合约需要运行在什么虚拟机上,目前支持的虚拟机是NEO和WASM。
- 调用智能合约invokeTransaction的过程,sdk中具体做了什么
//step1:构造交易
//需先将智能合约参数转换成vm可识别的opcode
Transaction tx = ontSdk.vm().makeInvokeCodeTransaction(ontContractAddr, null, contract.toArray(), VmType.Native.value(), sender.toBase58(),gaslimit,gasprice);
//step2:对交易签名
ontSdk.signTx(tx, info1.address, password);
//step3:发送交易
ontSdk.getConnectMgr().sendRawTransaction(tx.toHexString());
- invoke时为什么要传入账号和密码
调用智能合约时需要用户签名,如果是预执行不需要签名,钱包中保存的是加密后的用户私钥,需要密码才能解密获取私钥。
- 查询资产操作时,智能合约预执行是怎么回事,如何使用?
如智能合约get相关操作,从智能合约存储空间里读取数据,无需走节点共识,只在该节点执行即可返回结果。发送交易时调用预执行接口。
String result = (String) sdk.getConnect().sendRawTransactionPreExec(txHex);