前言
nep7协议的发布,新增加了VerificationR和ApplicationR两种合约触发器。使合约能够拒绝接受资产,或者在接受资产的同时能够做一些操作。同时nep5协议也更新了transfer时需要验证目标账户是不是允许获得资产。这要求我们在发布合约时要对合约是否可收钱进行设置。而这个功能NeoGui的支持更新不是很到位,所以在本文中我们将利用Nel的轻钱包(thinwallet)来发布和升级合约。
在本文中会分为两部分,首先会介绍利用thinwallet发布和升级合约的操作流程,然后会介绍相关例子代码。例子的实现利用了nel大佬李总写的sdk来构造和解析数据。最下方会附上本文中涉及工具以及资讯的地址。
正文
一 准备工作
首先我们编写一个智能合约方便用以下面的流程,编译出对应的avm。
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
using System;
using System.Numerics;
namespace NeoContract1
{
public class Contract1 : SmartContract
{
public static object Main(string method, object[] args)
{
var magicstr = "NEL";
if (Runtime.Trigger == TriggerType.Verification)//取钱才会涉及这里
{
return true;
}
else if (Runtime.Trigger == TriggerType.VerificationR)//取钱才会涉及这里
{
return true;
}
else if (Runtime.Trigger == TriggerType.Application)
{
if (method == "put")
{
Storage.Put(Storage.CurrentContext, "put","1");
return true;
}
if (method == "get")
{
return Storage.Get(Storage.CurrentContext, "put");
}
}
return false;
}
}
}
二 利用thinwallet发布合约
首先我们打开thinwallet主页面,并导入私钥。你可以在AccountInfo框中看到你所有的资产。由于发布合约是需要花费一定gas的,所以你需要在input框中拖入一定数量的utxo。
点击发布合约按钮打开如下图页面,导入我们刚才生成的avm格式的合约文件,并设置相关参数。
其中Parameter List和Return Type 是智能合约参数和返回值。具体的需求可以参考http://docs.neo.org/zh-cn/sc/Parameter.html
在我们的例子中我们的参数是(string method, object[] args)返回值是object 所以我们这里的参数设置为0710返回值为05
Need Storage ,Need DynCall,Can Charge 这三个确认按键代表的是合约是否需要存储区,是否需要动态调用,能否收钱
合约的hash是0x297c78a028bc387b71801be0156f9952ca5925ad
点击ok键回到主页面,点击test获得发布合约所需gas,由于例子合约中使用了存储空间,所以花费是490
点击Publish中的键进行签名和发送交易,等待交易确认。
· 等交易确认后,我们点击AppCall键,来到下图的页面,输入我们刚才合约的hash(0x297c78a028bc387b71801be0156f9952ca5925ad),点击LoadScript。如果参数框出现参数,则代表合约发布成功。
接下来我们测试一下合约的put和get方法,首先将下方的textParam改成put发送交易,然后重复流程改成get,点击test,可以得到值。这里只附上调用get的图,可以看到获取到了存入的“1”
三 发布合约的代码介绍
首先合约的发布是调用Neo.Contract.Create(byte[] script, byte[] parameter_list, byte return_type, bool need_storage, string name, string version, string author, string email, string description)函数来执行的。其中script是合约代码,parameter_list是参数列表,return_type是合约的返回值,name是合约的名称,version是版本号,author是作者,email是作者邮箱,description是合约的描述。
其中need_storage这个参数单独拉出来讲,是因为need_storage从字面意思和官方介绍中都为是否需要持久化存储区。但随着nep4和nep7协议的发布,这个字段也增加了对是否动态调用以及是否为可收钱合约的兼容。现在的need_storage=need_storage | need_nep4 | need_canCharge; 其中的need_storage为是否需要存储区(0false,1true),need_nep4为是否需要动态调用(0false,2true),need_canCharge为是否为可收钱合约(0false,4true)。也就是说如果你的合约需要存储区也需要动态调用并且为可收钱合约,那么这个字段的值应该为7.
接下来附上代码片段,例子详情可见https://github.com/NewEconoLab/neo-thinsdk-cs/blob/master/smartContractDemo/tests/others/PubScDemo.cs 。
发布合约的构造代码如下
ThinNeo.ScriptBuilder sb = new ThinNeo.ScriptBuilder()
//倒叙插入数据
sb.EmitPushString(description);
sb.EmitPushString(email);
sb.EmitPushString(auther);
sb.EmitPushString(version);
sb.EmitPushString(name);
sb.EmitPushNumber(need_storage|need_nep4| need_canCharge);
sb.EmitPushBytes(return_type);
sb.EmitPushBytes(parameter__list);
sb.EmitPushBytes(script);
sb.EmitSysCall("Neo.Contract.Create");
string scriptPublish = ThinNeo.Helper.Bytes2HexString(sb.ToArray());
然后调用invokeScript来获得虚拟机执行这段脚本所需花费的gas(你可以调用本地cli或者nelApi的invoke来获得花费)
byte[] postdata;
var url = Helper.MakeRpcUrlPost(api, "invokescript", out postdata, new MyJson.JsonNode_ValueString(scriptPublish));
var result = await Helper.HttpPost(url, postdata);
//string result = http.Post(api, "invokescript", new MyJson.JsonNode_Array() { new MyJson.JsonNode_ValueString(scriptPublish) },Encoding.UTF8);
var consume =((( MyJson.Parse(result) as MyJson.JsonNode_Object)["result"] as MyJson.JsonNode_Array)[0] as MyJson.JsonNode_Object)["gas_consumed"].ToString();
decimal gas_consumed = decimal.Parse(consume);
最后构造交易数据 下图中的makeTran是对tran的inputs和outputs进行构造
ThinNeo.InvokeTransData extdata = new ThinNeo.InvokeTransData();
extdata.script = sb.ToArray();
//Console.WriteLine(ThinNeo.Helper.Bytes2HexString(extdata.script));
extdata.gas = Math.Ceiling(gas_consumed-10);
//拼装交易体
ThinNeo.Transaction tran = makeTran(dir,null, new ThinNeo.Hash256(id_GAS), extdata.gas);
tran.version = 1;
tran.extdata = extdata;
tran.type = ThinNeo.TransactionType.InvocationTransaction;
byte[] msg = tran.GetMessage();
byte[] signdata = ThinNeo.Helper.Sign(msg, prikey);
tran.AddWitness(signdata, pubkey, address);
string txid = tran.GetHash().ToString();
byte[] data = tran.GetRawData();
string rawdata = ThinNeo.Helper.Bytes2HexString(data);
ThinNeo.Transaction makeTran(Dictionary<string, List<Utxo>> dir_utxos, string targetaddr, ThinNeo.Hash256 assetid, decimal sendcount)
{
if (!dir_utxos.ContainsKey(assetid.ToString()))
throw new Exception("no enough money.");
List<Utxo> utxos = dir_utxos[assetid.ToString()];
var tran = new ThinNeo.Transaction();
tran.type = ThinNeo.TransactionType.ContractTransaction;
tran.version = 0;//0 or 1
tran.extdata = null;
tran.attributes = new ThinNeo.Attribute[0];
var scraddr = "";
utxos.Sort((a, b) =>
{
if (a.value > b.value)
return 1;
else if (a.value < b.value)
return -1;
else
return 0;
});
decimal count = decimal.Zero;
List<ThinNeo.TransactionInput> list_inputs = new List<ThinNeo.TransactionInput>();
for (var i = 0; i < utxos.Count; i++)
{
ThinNeo.TransactionInput input = new ThinNeo.TransactionInput();
input.hash = utxos[i].txid;
input.index = (ushort)utxos[i].n;
list_inputs.Add(input);
count += utxos[i].value;
scraddr = utxos[i].addr;
if (count >= sendcount)
{
break;
}
}
tran.inputs = list_inputs.ToArray();
if (count >= sendcount)//输入大于等于输出
{
List<ThinNeo.TransactionOutput> list_outputs = new List<ThinNeo.TransactionOutput>();
//输出
if (sendcount > decimal.Zero && targetaddr != null)
{
ThinNeo.TransactionOutput output = new ThinNeo.TransactionOutput();
output.assetId = assetid;
output.value = sendcount;
output.toAddress = ThinNeo.Helper.GetPublicKeyHashFromAddress(targetaddr);
list_outputs.Add(output);
}
//找零
var change = count - sendcount;
if (change > decimal.Zero)
{
ThinNeo.TransactionOutput outputchange = new ThinNeo.TransactionOutput();
outputchange.toAddress = ThinNeo.Helper.GetPublicKeyHashFromAddress(scraddr);
outputchange.value = change;
outputchange.assetId = assetid;
list_outputs.Add(outputchange);
}
tran.outputs = list_outputs.ToArray();
}
else
{
throw new Exception("no enough money.");
}
return tran;
}
其中所用到的构造脚本的scriptBuilder和构造交易的Transaction都是用了李总写的sdk。如果你要用原生的Transaction和ScriptBuilder,这里就不附上代码了。原理一样,自己对比。
升级的相关内容会在NEO智能合约之发布和升级(二)继续。
thinwallet https://github.com/NewEconoLab/neo-thinsdk-cs thinWallet工程
sdk for neo (c#) https://github.com/NewEconoLab/neo-thinsdk-cs thinSDK工程
nep协议 https://github.com/neo-project/proposals