在 NEO 上发布代币

在 NEO 上发布代币合约

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的智能合约的api。

规范:

在下面的方法中,我们提供了在合约中函数的定义方式及参数调用。

方法:

  • 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 资产的转账信息了。

你可能感兴趣的:(在 NEO 上发布代币)