NEO智能合约之发布和升级(二)

昔言。 7月前 284

接NEO智能合约之发布和升级(一),我们接下来说说智能合约的升级功能。

一    准备工作

        合约的升级需要在合约内预先设置好升级接口,以方便在升级时调用。接下来我们对NEO智能合约之发布和升级(一)中的合约例子进行改造,添加升级接口。并发布合约得到合约的hash(0x8c4994ccf1c123f91090d07568653e54d74f307d),调用put方法在存储区存入值。

using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
using Neo.SmartContract.Framework.Services.System;
using Helper = Neo.SmartContract.Framework.Helper;
using System;using System.ComponentModel;
using System.Numerics;

namespace NeoContract1
{    
    public class Contract1 : SmartContract
    {        
        static readonly byte[] superAdmin = 
        Helper.ToScriptHash("ALjSnMZidJqd18iQaoCgFun6iqWRm2cVtj");//管理员

        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");
                }               
                if (method == "upgrade")//合约的升级就是在合约中要添加这段代码来实现
                {                    //不是管理员 不能操作
                    if (!Runtime.CheckWitness(superAdmin))   
                           return false;                    
                    if (args.Length != 1 && args.Length != 9)                 
                           return false;                    
                           byte[] script = Blockchain.GetContract(ExecutionEngine.ExecutingScriptHash).Script;                    
                           byte[] new_script = (byte[])args[0];                    //如果传入的脚本一样 不继续操作
                    if (script == new_script)                        
                           return false;                    
                           byte[] parameter_list = new byte[] { 0x07, 0x10 };                    
                           byte return_type = 0x05;                    
                           bool need_storage = (bool)(object)05;                    
                           string name = "test";                    
                           string version = "1.1";                    
                           string author = "NEL";                    
                           string email = "0";                    
                           string description = "test";                    
                           if (args.Length == 9)
                           {
                                  parameter_list = (byte[])args[1];
                                  return_type = (byte)args[2];
                                  need_storage = (bool)args[3];
                                  name = (string)args[4];
                                  version = (string)args[5];
                                  author = (string)args[6];
                                  email = (string)args[7];
                                  description = (string)args[8];
                           }
                    Contract.Migrate(new_script, parameter_list, return_type, need_storage, name, version, author, email, description); 
                    return true;
                }

            }            
            return false;
        }
    }
}

        代码中我们添加了upgrade的方法用以合约升级。在升级方法中,我们需要验证权限以确保安全性,然后就可以调用升级函数进行合约的升级。合约升级之后,原合约会被销毁,存储区会被移到新的合约。

二    利用thinwallet升级合约

    在发布上面的合约之后,我想增加一个delete功能,来删除某个存储。于是我对原合约进行修改,添加了delete方法。重新编译,得到新的合约,hash(0x53ab4dfdae199b8d76f0eac8363fb07e652aef1f)。

using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
using Neo.SmartContract.Framework.Services.System;
using Helper = Neo.SmartContract.Framework.Helper;
using System;using System.ComponentModel;
using System.Numerics;

namespace NeoContract1
{    
    public class Contract1 : SmartContract
    {        
        static readonly byte[] superAdmin = 
        Helper.ToScriptHash("ALjSnMZidJqd18iQaoCgFun6iqWRm2cVtj");//管理员

        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");
                  }                
                  if (method == "delete")
                  {
                       Storage.Delete(Storage.CurrentContext, "put");
                  }                
                  if (method == "upgrade")
                  {                    //不是管理员 不能操作
                       if (!Runtime.CheckWitness(superAdmin))                        
                             return false;                    
                       if (args.Length != 1 && args.Length != 9)                        
                             return false;                    
                             byte[] script = Blockchain.GetContract(ExecutionEngine.ExecutingScriptHash).Script; 
                             byte[] new_script = (byte[])args[0];                    //如果传入的脚本一样 不继续操作
                       if (script == new_script)                        
                             return false;                    
                             byte[] parameter_list = new byte[] { 0x07, 0x10 };                    
                             byte return_type = 0x05;                    
                             bool need_storage = (bool)(object)05;                    
                             string name = "test";                    
                             string version = "1.1";                    
                             string author = "NEL";                    
                             string email = "0";                    
                             string description = "test";                    
                       if (args.Length == 9)
                       {
                              parameter_list = (byte[])args[1];
                              return_type = (byte)args[2];
                              need_storage = (bool)args[3];
                              name = (string)args[4];
                              version = (string)args[5];
                              author = (string)args[6];
                              email = (string)args[7];
                              description = (string)args[8];
                       }
                       Contract.Migrate(new_script, parameter_list, return_type, need_storage, name, version, author, email, description);                    return true;
                   }

               }            
               return false;
        }
    }
}

因为我想保留原合约的存储区,所以我用升级功能来升级合约。

打开thinwallet,点击Upgrade Sc(升级合约按钮),出现如下页面,填入相关数据。

点击确认回到主页面,点击test按钮。你会发现test之后返回的状态是Fault,执行失败。那是因为在升级合约里,我们验证了调用者的权限,invoke调用并没有签名这一步。所以我们需要自己在output里自己添加一条丢弃掉。费用自己估算。

点击发送交易得到交易id,等待交易确认。

交易确认后我们AppCall原合约(0x8c4994ccf1c123f91090d07568653e54d74f307d),发现合约不存在。

接下来我们AppCall新合约(0x53ab4dfdae199b8d76f0eac8363fb07e652aef1f),发现合约存在,不调用put,直接调用get看是否能获取到值。

如下图,我们在没有调用put设置值的情况下还是get到了数据。说明存储区被新的合约所继承。

 

三    升级合约的代码介绍

        升级合约本质上就是调用原合约的升级函数来进行升级。调用Contract.Migrate方法,参数和create相同。这里就不重复介绍了,详情见NEO智能合约之发布和升级(一)。

        升级合约的构造代码如下

ThinNeo.ScriptBuilder sb = new ThinNeo.ScriptBuilder();
//倒叙插入数据
var array = new MyJson.JsonNode_Array();
array.AddArrayValue("(bytes)" + str_script);//新的合约代码
array.AddArrayValue("(bytes)0710");
array.AddArrayValue("(bytes)05");
array.AddArrayValue("(int)"+ 5);
array.AddArrayValue("(str)合约测试");//name
array.AddArrayValue("(str)1");//version
array.AddArrayValue("(str)ss");//author
array.AddArrayValue("(str)1");//email
array.AddArrayValue("(str)sssss");//desc
sb.EmitParamJson(array);//参数倒序入
sb.EmitParamJson(new MyJson.JsonNode_ValueString("(str)upgrade"));
var shash = Config.dapp_sgas; //原合约
hashsb.EmitAppCall(shash);

         最后构造交易数据  下图中的makeTran是对tran的inputs和outputs进行构造

ThinNeo.InvokeTransData extdata = new ThinNeo.InvokeTransData();
extdata.gas = 500;// Math.Ceiling(gas_consumed - 10);
extdata.script = sb.ToArray();

//拼装交易体
ThinNeo.Transaction tran = Helper.makeTran(dir[Config.id_GAS], null, new ThinNeo.Hash256(Config.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);
url = Helper.MakeRpcUrlPost(Config.api_local, "sendrawtransaction", out postdata, new MyJson.JsonNode_ValueString(rawdata));
result = await Helper.HttpPost(url, postdata);
        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,这里就不附上代码了。原理一样,自己对比。

 

 

thinwallet    https://github.com/NewEconoLab/neo-thinsdk-cs    thinWallet工程

sdk for neo (c#)     https://github.com/NewEconoLab/neo-thinsdk-cs    thinSDK工程

文中的例子  https://github.com/NewEconoLab/neo-thinsdk-cs/blob/master/smartContractDemo/tests/others/MigrateScDemo.cs

作者:NNS核心开发者——印炜

最后于 7月前 被昔言。编辑 ,原因:
最新回复 (0)
全部楼主
返回