作者: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 資產的轉賬資訊了。