本教程分享了一些實用的技巧供您參考。 開發C#NEO智能合約的最大挑戰之一是NeoVM支持的語言特性,實際操作中使用的特性比官方文檔提供的要多。 還有一些關於存儲交互與隨機生成的實用技巧。 Enjoy hacking.

類型轉換

NeoVM支持的基本類型是位元組數組(Byte []),然後是常用的Boolean,String和BigInteger。 還有其他整數類型,如Int8,Int64,UInt16,long,ulong等,這些可以被隱式轉換為BigInteger。 Float類型不受支持。

所以我們只要關注Byte [],Boolean,String和BigInteger之間的轉換。 注意:有些轉換不是官方定義的,在這種情況下,我會嘗試做出最合理的實現。

Byte[] to Boolean

雖然這個看起來是最簡單的,但實際上它沒有直接轉換。官方說明中只提到False等於整數 0。我們假設True等於所有其他值,而空位元組數組等於False。所以我們定義了以下幾個函數:

public static bool bybool (byte[] data) => data[0] != 0;

然後可以得到如下結果:

bool b0 = Bytes2Bool(new byte[0]); //False
bool b1 = Bytes2Bool(new byte[1]{0}); //False
bool b2 = Bytes2Bool(new byte[1]{1}); //True
bool b3 = Bytes2Bool(new byte[2]{0,2}); //False
bool b4 = Bytes2Bool(new byte[3]{3,2,5}); //True

Byte[] to String

這個轉換直接由Neo.SmartContract.Framework.Helper提供

public static string BytesToByte(byte[] data) => data.AsString();

Byte[] to BigInteger

public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();

Boolean to Byte[]

這個也需要手工轉換。

public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});

String to Byte[]

public static byte[] StringToByteArray(String str) => str.AsByteArray();

BigInteger to Byte[]

public static byte[]
BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();

Byte to Byte[]

你可能會認為下面這段代碼看起來很好:

public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!

它可以通過編譯,但在大多數情況下會返回意想不到的值。 這是因為不支持按照變數分配位元組數組。 所以要避免使用這種轉換。

操作符和關鍵字

正如官方文檔中提到的,NeoVM支持大多數的c#操作符和關鍵字。補充說明如下:

Bool: AND, OR NOT

支持操作符「&&」,「||」 「!」

bool b = true;
bool a = false;
Runtime.Notify(!b, b && a, b || a);// 分別代表false, false, true

關鍵字: 「ref」 「out」

關鍵字「ref」或「out」是C#語言的特性,用來允許將局部變數傳給函數作為引用。Neo智能合約不支持這些關鍵字。

關鍵字: 「try-catch」, 「throw」, 「finally」

不支持這幾個用於異常處理的關鍵字

位元組數組:級聯和子數組

//Concatenation
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);

//Get Byte arrays subarray
public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);

關鍵字 參數中的「This」

有時你需要定義類型的擴展,從而使邏輯更加簡潔直觀。 NeoVM支持關鍵字「This」。 以下示例代碼顯示了如何使用它。

// Write a static class for the extentions of byte array
public static class ByteArrayExts{
// Return The subarray
public static byte[] Sub(this byte[] bytes, int start, int len){
return Helper.Range(bytes, start, len);
}
// Return the reversed bytearray
public static byte[] Reverse(this byte[] bytes){
byte[] ret = new byte[0];
for(int i = bytes.Length -1 ; i>=0 ; i--){
ret = ret.Concat(bytes.Sub(i,1));
}
return ret;
}
}

使用上面的方法:

byte[] ba0 = {1,31,41,111};
byte[] ba1 = {12,6,254,0,231};
//Calls the Reverse and Sub functions with only one line.
Runtime.Notify(ba0, ba1, ba0.Reverse(), ba1.Sub(1,2));
//Call the extension functions multiple times in a row.
Runtime.Notify(ba1.Sub(0,3).Reverse());

位元組數組:修改值

NeoVM不支持可變位元組操作。 所以我們需要拆分子數組,修改其中的一部分值,然後再將它們連接起來。 應將下面這個方法放入上面的ByteArrayExts類中。

public static class ByteArrayExts{
//... previous functions ...

public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
byte[] part1 = bytes.Sub(0,start);
int endIndex = newBytes.Length + start;
if(endIndex < bytes.Length){
byte[] part2 = bytes.Sub(endIndex, bytes.Length-endIndex);
return part1.Concat(newBytes).Concat(part2);
}
else{
return part1.Concat(newBytes);
}
}
}

使用:

byte[] orig = new byte[5]{1,2,3,4,5};
byte[] newValue = new byte[2]{6,7};

//Replace the 3rd and 4th elements of orig byte array.
byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};

存儲

Storage / StorageMap類是與智能合約的鏈上持久化信息進行交互的唯一方式。 基本的CRUD操作是:

//Create and Update: 1GAS/KB
Storage.Put(Storage.CurrentContext, key, value);

//Read: 0.1GAS/time
Storage.Get(Storage.CurrentContext, key);

//Delete: 0.1GAS/time
Storage.Delete(Storage.CurrentContext, key);

在使用上面這幾個方法時,有一些技巧:

1.在調用Storage.Put()之前檢查值是否保持不變。 如果不改變,這將節省0.9GAS。

2.在調用Storage.Put()之前,檢查新值是否為空。 如果為空,請改用Storage.Delete()。 這也將節省0.9GAS。

byte[] orig = Storage.Get(Storage.CurrentContext, key);
if (orig == value) return;//Dont invoke Put if value is unchanged.

if (value.Length == 0){//Use Delete rather than Put if the new value is empty.
Storage.Delete(Storage.CurrentContext, key);
}
else{
Storage.Put(Storage.CurrentContext, key, value);
}

3. 設計數據結構時預估長度接近但小於n KB。因為方法寫2位元組和寫900位元組的開銷是一樣的。如有必要,你甚至可以組合一些項。

BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes.
int i = 0;
BigInteger batch = 0;
while( i< userIDs.Length){
byte[] record = new byte[0];
for(int j = 0; j< 31;j++){//31x32 = 992 Bytes.
int index = i + j;
if( index == userIDs.Length ) return;
else{
record=record.Concat(userIDs[index].AsByteArray());
}
}
//This cost only 1GAS rather than 31GAS.
Storage.Put(Storage.CurrentContext, batch.AsByteArray(), record);
batch = batch + 1;
++i;
}

隨機性

生成隨機值對於智能合約來說是一項挑戰。

首先,種子必須是區塊鏈相關的確定性值。 否則,記賬員就不能同意。 大多數Dapps會選擇blockhash作為種子。但是使用這種方法的話,不同的用戶在同一個區塊中調用相同的SC方法會返回相同的結果。在Fabio Cardoso的文章中,引入了一種新的演算法來同時使用blockhash和transactionID作為種子。

對於一些高度敏感的Dapps,專業用戶可能會爭辯說,記賬員可以通過重新排序交易來干預blockhashes。 在這種情況下,generalkim00和maxpown3r提供了非對稱熵的演算法。 這個過程有點複雜,所以要想學習的話,可以點擊這個鏈接閱讀他們這個博彩的例子的智能合約源代碼。

總結

感謝閱讀本教程。如果我們在開發智能合約時發現更多技巧的話,我會繼續在這裡更新它。感謝dprat0821在討論中給予的幫助。感謝Fabio, generalkim00和maxpown3r的精彩想法。

我的團隊正在開發一款將人們內心深處的話語刻在NEO區塊鏈上的遊戲。謝謝你的意見和建議。

NEO 捐贈

地址: AKJEavjHZ3v96kxh7nWKpt4nVCj7VtirCg

原文鏈接:medium.com/@gongxiaojin

翻譯:包子


NEOFANS:neofans.org

NEOFANS 微博:weibo.com/neofanscommun

NEOFANS telegram群:t.me/NEOfansCN


推薦閱讀:
相关文章