在 NEO 上釋出代幣合約

NEOWest發表於2018-12-11

作者:39acd4af77

NEO 系統中有 UTXO 模型的代幣 NEO 和 GAS,但如果基於 NEO 開發 Dapp 或其他區塊鏈專案時我們仍然需要能實現自己的代幣功能,這裡就來總結一下如何用智慧合約在 NEO 上釋出代幣。
與比特幣的 BIP、以太坊的 ERC 等類似,NEO 也有 NEPs: NEO Enhancement Proposals NEO加強/改進提議 ,它描述的是 NEO 平臺的標準,包括核心協議規範,客戶端 API 和合約標準。
在 NEO 公鏈上釋出代幣是 Nep5 即 NEO 5 號改進提案中提出的,所以我們釋出的代幣統稱 Nep5 代幣,在 Nep5 中,規定了代幣合約必須實現的介面、返回結果等標準,所以我們只需按照標準就可以開發自己的代幣,然後釋出合約即可,下面就分別講講合約程式設計和釋出。
合約的基本介紹和開發前準備可以參考 NEO 開發文件中的智慧合約部分,如果想更全面的學習智慧合約開發可以看這裡相關資料。

nep5:

概述:

nep5提案描述了neo區塊鏈的token標準,它為token類的智慧合約提供了系統的通用互動機制,定義了這種機制和每種特性,並提供了開發模板和示例。
動機:隨著neo區塊鏈生態的發展,智慧合約的部署和呼叫變得越來越重要,如果沒有標準的互動方法,系統就需要為每個智慧合約維護一套單獨的api,無論合約間有沒有相似性。
token類合約的操作機制其實基本都是相同的,因此需要這樣一套標準。這些與token互動的標準方案使整個生態系統免於維護每個使用token的智慧合約的pai。

規範:

在下面的方法中,我們提供了在合約中函式的定義方式及引數呼叫。

方法:

  • totalSupply
    public static BigInteger totalSupply()
    返回token總量。
  • name
    public static string name()
    返回token名稱
    每次呼叫時此方法必須返回相同的值。
  • symbol
    public static string symbol()
    返回此合約中管理的token的簡稱,3-8字元、限制為大寫英文字母;
    每次呼叫時此方法必須返回相同的值。
  • decimals
    public static byte decimals()
    返回token使用的小數位數;
    每次呼叫時此方法必須返回相同的值。
  • balanceOf
    public static BigInteger balanceOf(byte[] account)
    返回token餘額
    引數account應該是一個20位元組的地址;
    如果account是未使用的地址,則此方法必須返回0。
  • transfer
    public static bool transfer(byte[] from, byte[] to, BigInteger amount)
    將amount數量的token從from賬戶轉到to賬戶;
    引數amount必須大於或等於0;
    如果from帳戶餘額沒有足夠的token可用,則該函式必須返回false;
    如果該方法成功,它必須觸發transfer事件,並且必須返回true,即使amount是0,或from與to相同;
    函式應該檢查from地址是否等於合約呼叫者hash,如果是,應該處理轉賬; 如果不是,該函式應該使用SYSCALL Neo.Runtime.CheckWitness來驗證交易;
    如果to地址是已部署的合約地址,則該函式應該檢查該合約的payable標誌以決定是否應該將token轉移到該合約地址;如果未處理轉賬,則函式應該返回false。

開發合約

下面就是一個 Nep5 代幣合約的具體實現:

using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
using System;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Linq.Expressions;
using System.Numerics;
using System.Runtime.Remoting.Messaging;
using Neo.SmartContract.Framework.Services.System;
using Helper = Neo.SmartContract.Framework.Helper;

namespace ABCContract
{
    public class ABC : SmartContract
    {
        public delegate void deleTransfer(byte[] from, byte[] to, BigInteger value);
        [DisplayName("transfer")]
        public static event deleTransfer Transferred;

        public class TransferInfo
        {
            public byte[] from;
            public byte[] to;
            public BigInteger value;
        }

        //發幣管理員賬戶,改成自己測試用的的
        private static readonly byte[] superAdmin = Helper.ToScriptHash("APVdDEtthapuaPedMHCgrDR5Vyc22fns9m");

        public static string name()
        {
            return "ABC Coin";//名稱
        }

        public static string symbol()
        {
            return "ABC";//簡稱
        }

        private const ulong factor = 100000000;//精度
        private const ulong totalCoin =  100000000 * factor;//總量 要乘以精度,NEO 系統中沒有小數,所有數字型別都轉為 BigInteger 處理

        public static byte decimals()
        {
            return 8;
        }

        public static object Main(string method, object[] args)
        {
            var magicstr = "abc-test";
            if (Runtime.Trigger == TriggerType.Verification)
            {
                return false;
            }
            else if (Runtime.Trigger == TriggerType.VerificationR)
            {
                return true;
            }
            else if (Runtime.Trigger == TriggerType.Application)
            {
                //開始時取到呼叫該合約的指令碼hash
                var callscript = ExecutionEngine.CallingScriptHash;

                if (method == "totalSupply")
                    return totalSupply();
                if (method == "name")
                    return name();
                if (method == "symbol")
                    return symbol();
                if (method == "decimals")
                    return decimals();
                //發行,合約釋出後由管理員發行代幣
                if (method == "deploy")
                {
                    if (!Runtime.CheckWitness(superAdmin))
                        return false;
                    byte[] total_supply = Storage.Get(Storage.CurrentContext, "totalSupply");
                    if (total_supply.Length != 0)
                        return false;
                    var keySuperAdmin = new byte[] {0x11}.Concat(superAdmin);
                    Storage.Put(Storage.CurrentContext, keySuperAdmin, totalCoin);
                    Storage.Put(Storage.CurrentContext, "totalSupply", totalCoin);

                    Transferred(null, superAdmin, totalCoin);
                }

                //獲取餘額
                if (method == "balanceOf")
                {
                    if (args.Length != 1)
                        return 0;
                    byte[] who = (byte[]) args[0];
                    if (who.Length != 20)
                        return false;
                    return balanceOf(who);
                }

                //轉賬介面
                if (method == "transfer")
                {
                    if (args.Length != 3)
                        return false;
                    byte[] from = (byte[]) args[0];
                    byte[] to = (byte[]) args[1];
                    if (from == to)
                        return true;
                    if (from.Length != 20 || to.Length != 20)
                        return false;
                    BigInteger value = (BigInteger) args[2];
                    if (!Runtime.CheckWitness(from))
                        return false;
                        //禁止跳板呼叫
                    if (ExecutionEngine.EntryScriptHash.AsBigInteger() != callscript.AsBigInteger())
                        return false;
                    if (!IsPayable(to))
                        return false;
                    return transfer(from, to, value);
                }

                //合約指令碼的轉賬介面、彌補沒有跳板呼叫
                if (method == "transfer_app")
                {
                    if (args.Length != 3)
                        return false;
                    byte[] from = (byte[]) args[0];
                    byte[] to = (byte[]) args[1];
                    BigInteger value = (BigInteger) args[2];

                    if (from.AsBigInteger() != callscript.AsBigInteger())
                        return false;
                    return transfer(from, to, value);
                }

                //獲取交易資訊
                if (method == "getTxInfo")
                {
                    if (args.Length != 1)
                        return 0;
                    byte[] txid = (byte[]) args[0];
                    return getTxInfo(txid);
                }

            }

            return false;

        }

        //獲取總量
        private static object totalSupply()
        {
            return Storage.Get(Storage.CurrentContext, "totalSupply").AsBigInteger();
        }

        //交易
        private static bool transfer(byte[] from, byte[] to, BigInteger value)
        {
            if (value <= 0)
                return false;
            if (from == to)
                return true;
            if (from.Length > 0)
            {
                var keyFrom = new byte[] {0x11}.Concat(from);
                BigInteger from_value = Storage.Get(Storage.CurrentContext, keyFrom).AsBigInteger();
                if (from_value < value)
                    return false;
                if (from_value == value)
                    Storage.Delete(Storage.CurrentContext, keyFrom);
                else
                {
                    Storage.Put(Storage.CurrentContext, keyFrom, from_value - value);
                }
            }

            if (to.Length > 0)
            {
                var keyTo = new byte[] {0x11}.Concat(to);
                BigInteger to_value = Storage.Get(Storage.CurrentContext, keyTo).AsBigInteger();
                Storage.Put(Storage.CurrentContext, keyTo, to_value + value);
            }

            setTxInfo(from, to, value);
            Transferred(from, to, value);
            return true;
        }

        private static void setTxInfo(byte[] from, byte[] to, BigInteger value)
        {
            TransferInfo info = new TransferInfo();
            info.@from = from;
            info.to = to;
            info.value = value;
            byte[] txInfo = Helper.Serialize(info);
            var txid = (ExecutionEngine.ScriptContainer as Transaction).Hash;
            var keyTxid = new byte[] {0x13}.Concat(txid);
            Storage.Put(Storage.CurrentContext, keyTxid, txInfo);
        }

        private static object balanceOf(byte[] who)
        {
            var keyAddress = new byte[] {0x11}.Concat(who);
            return Storage.Get(Storage.CurrentContext, keyAddress).AsBigInteger();
        }

        private static TransferInfo getTxInfo(byte[] txid)
        {
            byte[] keyTxid=new byte[] {0x13}.Concat(txid);
            byte[] v = Storage.Get(Storage.CurrentContext, keyTxid);
            if (v.Length == 0)
                return null;
            return Helper.Deserialize(v) as TransferInfo;
        }

        public static bool IsPayable(byte[] to)
        {
            var c = Blockchain.GetContract(to);
            if (c.Equals(null))
                return true;
            return c.IsPayable;
        }
    }
}
複製程式碼

釋出合約

前面的合約編譯後生成 avm 檔案,接下來就可以拿著 avm 檔案去釋出合約了,這裡有完整的釋出合約教程可以參考,此處就不展開了:NEO 智慧合約釋出和升級

合約釋出成功後,就要使用合約中預置的管理員簽名來發行代幣,可以參考下面程式碼來呼叫合約的 deploy 介面發行代幣:

//Helper相關方法是引用了https://www.nuget.org/packages/Neo.sdk.thin
private static void DeployNep5Token(byte[] prikey)
    {
        var array = new JArray();
        array.Add("(int)" + 1); //deploy介面不需要引數,這裡傳個1
        sb.EmitParamJson(array); //引數倒序入
        sb.EmitPushString("deploy");
        sb.EmitAppCall(new Hash160("hash");//hash是剛才釋出的合約的hash
        script = sb.ToArray();

        //私鑰用合約中預留管理員的私鑰、deploy介面需要驗證簽名
        byte[] pubkey = Helper_NEO.GetPublicKey_FromPrivateKey(prikey);
        string address = Helper_NEO.GetAddress_FromPublicKey(pubkey);

        Transaction tran = new Transaction();
        tran.inputs = new TransactionInput[0];
        tran.outputs = new TransactionOutput[0];
        tran.attributes = new ThinNeo.Attribute[1];
        tran.attributes[0] = new ThinNeo.Attribute();
        tran.attributes[0].usage = TransactionAttributeUsage.Script;
        tran.attributes[0].data = pubkey;
        tran.version = 1;
        tran.type = TransactionType.InvocationTransaction;

        var idata = new InvokeTransData();
        tran.extdata = idata;
        idata.script = script;
        idata.gas = 0;

        byte[] msg = tran.GetMessage();
        string msgstr = Helper.Bytes2HexString(msg);
        byte[] signdata = Helper_NEO.Sign(msg, prikey);
        tran.AddWitness(signdata, pubkey, address);
        string txid = tran.GetHash().ToString();
        byte[] data = tran.GetRawData();
        string rawdata = Helper.Bytes2HexString(data);
        //neoapi是neo cli節點的url
        var result = HttpGet($"{neoapi}?method=sendrawtransaction&id=1¶ms=[\"{rawdata}\"]");
        var json = JObject.Parse(result);
    }複製程式碼

到此為止已經在 NEO 上發行了自己的代幣,可以使用 ApplicationLogs 提供的 API 來查詢 Nep5 資產的轉賬資訊了。


相關文章