Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

开发NEO智能合约的实用技巧

一点评谱
109 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.' S0 k4 w1 I' n  X5 s5 o1 x: A
类型转换
0 d  C; W1 p9 XNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。
! B* [4 a2 B* `8 R. D6 a' v' e所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。6 a" q5 T  Y: }: y3 c
Byte[] to Boolean
, V1 B1 X: i1 m) W虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:
5 S8 s( ?$ O( p! V1 s8 ]2 @/ wpublic static bool bybool (byte[] data) => data[0] != 0;
9 t1 r- B6 f8 H/ g/ F& ]5 [, w2 i然后可以得到如下结果:
3 @" y- U; ]& x/ j( ?, [/ {* [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 # x! w5 e& M: i6 P
Byte[] to String  `7 H1 @# k, p
这个转换直接由Neo.SmartContract.Framework.Helper提供9 C: ^8 h6 O. ~
public static string BytesToByte(byte[] data) => data.AsString();
0 m' y$ t# W0 H; N: Q+ B3 ?Byte[] to BigInteger% C0 i! ]) r" [$ u* V% p
public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
. h# x. k' y! Z8 B5 }9 ?0 TBoolean to Byte[]6 K6 I6 L- Q8 Q: N! Q: I' j, S
这个也需要手工转换。
, o8 r4 d+ _1 Q* vpublic static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});
! r* c+ L# V2 V* {* ]3 a* qString to Byte[]5 m" c) [* q! L3 I! z% s
public static byte[] StringToByteArray(String str) => str.AsByteArray();% i/ s7 ?5 t/ z" ~9 P
BigInteger to Byte[]
4 z6 ^) a5 Z- V) |$ N6 s) }+ mpublic static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();
, e  |( K( B" z" e1 @Byte to Byte[]
9 [  x$ W2 d- _% y; _# T; O  ^你可能会认为下面这段代码看起来很好:8 O. P3 l6 N, i. p
public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!: R- _2 p3 U% {& u
它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。
4 ?1 C( |- W1 m; ?( G# _* x操作符和关键字1 X, l7 b# s" \5 v! S8 K* E# F$ Z
正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:- k4 I- x( M& i! ~2 P' z9 F$ P
Bool: AND, OR 和 NOT
* u4 S+ I% t* b- l  E支持操作符**“&&”,“||”** 和** “!”**
. Q, J  ^6 t* {. X! a. ?5 ibool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true
% Y/ b% ~9 p, F, r4 z+ e5 R关键字: “ref” 和 “out”; k! W6 o/ ]1 z% ^
关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。
5 k% F0 B( g, C; H关键字: “try-catch”, “throw”, “finally”
  ]4 b5 `0 Z  T# j0 M7 a不支持这几个用于异常处理的关键字
& J5 i% r/ d! L5 U字节数组:级联和子数组7 W8 ~! v7 ^5 F' O
`//Concatenation8 \7 P8 L- W9 w. Q
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
8 W1 n: R: M* Z* N$ `9 R//Get Byte array’s subarray/ w  Z2 T/ N7 {# {
public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`
2 f) T* E0 a9 d& t5 _关键字 参数中的“This”
) z1 O1 y* C* ~& p6 e有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
# o9 ~- J, b7 o# o9 X0 J// 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;    } }3 Y. ~: d$ _1 w1 D3 `
使用上面的方法:
* A8 i' E4 T5 V3 w. p% T) R5 F. Z9 [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());+ w6 H7 _& }: R/ ~% q
字节数组:修改值0 B7 R& q5 g( x( b1 r
NeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。
; z- c$ w4 ?3 s5 l2 k  C4 Q5 ]`public static class ByteArrayExts{
# K/ p) q1 E3 `1 d1 {$ u//… previous functions …) H* u/ e: M; r' H, G7 k
public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){+ g3 {# ]4 W) S! O2 g
byte[] part1 = bytes.Sub(0,start);2 t. N. \2 ^% I8 `+ |* r3 @
int endIndex = newBytes.Length + start;3 p& N6 _# s% h6 _) [& @% O$ R+ |- X
if(endIndex
: T1 G" u1 i* G$ {4 O2 P4 X; e使用:, C; E' Y1 Y5 L0 n$ J
`byte[] orig = new byte[5]{1,2,3,4,5};
: k* Z3 z! L% z9 k0 s- s. `8 Nbyte[] newValue = new byte[2]{6,7};; H7 J8 x$ y  g! g! X: ?, d
//Replace the 3rd and 4th elements of orig byte array.# E, }4 d6 i, k& o" S
byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`6 a" D1 p1 Y) v% P5 m2 y' R. X
存储( w$ A7 K( L6 E5 d% ^9 ?1 Q0 {  i- @
Storage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:
0 B  Q2 z6 |% m1 {+ b2 r`//Create and Update: 1GAS/KB8 [; k0 y* j+ q
Storage.Put(Storage.CurrentContext, key, value);$ \3 I2 L% @) h% D& G
//Read: 0.1GAS/time
$ h' W) g8 b+ i: h6 }Storage.Get(Storage.CurrentContext, key);+ e$ p) r, L: {  y0 t
//Delete: 0.1GAS/time
, X% N0 I1 b2 O% T$ T) Y* KStorage.Delete(Storage.CurrentContext, key);`. \1 r  D3 q0 m3 Q! s
在使用上面这几个方法时,有一些技巧:! ]2 G/ J% |+ J: \2 S. y
1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。
; z2 ]& t7 S! M+ R; Q2 r  h; C2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。. ^  F" j6 z) Q7 Y; v
`byte[] orig = Storage.Get(Storage.CurrentContext, key);# x. n- K& o8 I, l, h! _! h$ ?
if (orig == value) return;//Don’t invoke Put if value is unchanged.6 H0 Y/ r" s2 P# T" _  K
if (value.Length == 0){//Use Delete rather than Put if the new value is empty.
2 Y8 _# z, @) l: n4 Q! c$ DStorage.Delete(Storage.CurrentContext, key);
6 w7 Q+ l( u6 k7 z, Q. Y* C; j+ y}( l9 Y9 v& F: S5 @7 i
else{$ G2 I! ~$ ]! g6 g2 X9 b7 z
Storage.Put(Storage.CurrentContext, key, value);
  E1 @1 Z6 x5 g9 [* T% Q0 Q}`
% S; n  y3 s% G0 e" n# o设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。: ?% D3 J/ l: {/ W& d3 b
9 a( O2 Z$ i: J: g, A
BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i
, {6 |  m7 f1 a7 ^* a随机性( D* e1 a( [$ \# z" c8 Z  _, o1 C
生成随机值对于智能合约来说是一项挑战。/ E3 N! _# \6 d9 y: p" J1 Z- m- Q
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。0 t- ^* _' ?9 ^4 z/ C
对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。7 x8 ~+ U% v# f, ]
总结$ r) H5 f5 v  p7 f
感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。$ F: `: m1 U- O6 I3 X9 {* F% ^
我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1