Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
108 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.
, ~* K. V/ P) U! h- {! B类型转换
9 y9 [( A- {5 z2 p! @: b2 r- m  ?* MNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。
4 _& a2 q( q& b9 D9 A6 R所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。
( Y6 g1 K* P6 H, t/ i2 |) AByte[] to Boolean
; d0 F' _7 K1 h* w) D4 ?虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:
4 r4 _6 p1 X! J2 }6 tpublic static bool bybool (byte[] data) => data[0] != 0;5 ?2 h9 B! G. W
然后可以得到如下结果:
+ q% J6 \- J8 ?  kbool 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   t. _8 b. l9 @: x
Byte[] to String
8 j+ a. l( Q# b4 g; w, W这个转换直接由Neo.SmartContract.Framework.Helper提供
& W2 B# j$ a- j1 }; w3 vpublic static string BytesToByte(byte[] data) => data.AsString();$ d8 n% j* w1 n& J1 C
Byte[] to BigInteger5 O" q' |+ M/ H; U1 a
public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
# o5 S5 |- n' V+ m) L% kBoolean to Byte[]
2 _) i0 s; u4 c. i; U8 j+ u  o' `: I这个也需要手工转换。% r* Z* ]: F0 b. o, m* r4 P6 G$ O6 A
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});
" M+ G3 h. Y- \String to Byte[]
3 b5 {6 d' _5 T. {; ]public static byte[] StringToByteArray(String str) => str.AsByteArray();
4 _4 m3 H  j- y, K$ D5 eBigInteger to Byte[]
0 s% C# l' i5 z9 R+ F3 opublic static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();+ F" L' j# e  i- F3 k, k
Byte to Byte[]
) ~9 E3 v, Y4 ^+ C3 o! r8 Y你可能会认为下面这段代码看起来很好:* c! F* I) s  Z' R- M% \
public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
% O9 n  Z" O/ B  E$ S+ H它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。
" ~2 }: Y6 }% k9 Q- g9 T操作符和关键字
* z8 L8 z9 K4 \5 ~1 _正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:  [& ^' Y$ n/ v" r0 b
Bool: AND, OR 和 NOT* T' V8 m# t3 W" s
支持操作符**“&&”,“||”** 和** “!”**0 T. V. j! i2 ]- M' T
bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true' H* B1 N2 W/ G. N" N
关键字: “ref” 和 “out”
1 s, ~) e6 T: m/ X关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。8 G6 a2 U3 v8 @$ e+ h% ~
关键字: “try-catch”, “throw”, “finally”
: o. p. S# f2 o+ D( E+ Z不支持这几个用于异常处理的关键字: s; Y' O% y* `" I+ N, G
字节数组:级联和子数组3 N; v/ m0 R+ [  w7 w  J$ p7 b
`//Concatenation( ~# @  W8 b) O) ?& r
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
" Y+ c, ]5 c6 m9 d# ]//Get Byte array’s subarray
/ x' h5 x) N% ?% ~public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`& L) x* H: o" E5 O; h( x! F9 z9 H
关键字 参数中的“This”9 c: z5 D6 R; v* |8 K% L
有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
* }; d1 G- v6 F8 T4 G- F// 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;    } }
; ?$ E- g; q% r* ?# l+ J8 K使用上面的方法:( o0 I) j: p8 u( v. y
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());
: k, N. U# s% z3 |6 x0 t" l% d字节数组:修改值- A) U% m/ x! t9 |
NeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。+ c( X8 h  f) t# m" Z4 y
`public static class ByteArrayExts{) ~) ~  c  Y; S& r5 J1 O8 p" L
//… previous functions …( U) B: }/ B- I6 P* x
public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
- J) A2 |3 f3 N0 w/ X$ O8 Kbyte[] part1 = bytes.Sub(0,start);
, U( V( V; D/ Z! r) Fint endIndex = newBytes.Length + start;$ B: l) g% G$ O7 r0 [# k5 R3 ]
if(endIndex
! `. f' G: Z6 [+ g: o5 L6 @使用:7 d* a; C5 y+ u3 |9 d, Q
`byte[] orig = new byte[5]{1,2,3,4,5};' [3 o$ I- O4 J' m4 L# ~4 H# Z
byte[] newValue = new byte[2]{6,7};
% F( n/ z/ P4 d$ l2 X//Replace the 3rd and 4th elements of orig byte array.
8 @; @' v5 l5 l! q% i* n: i/ Sbyte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`: o( U: \( e1 N) X! T6 B( [: C
存储
! V( s) P8 [' f- a" {Storage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:6 P' Y% B2 A/ L
`//Create and Update: 1GAS/KB% E5 m! \9 J. U; B5 R3 h
Storage.Put(Storage.CurrentContext, key, value);
% }( v% R. Q% [5 |/ M//Read: 0.1GAS/time0 y% I. R$ c$ U  T
Storage.Get(Storage.CurrentContext, key);* X: T  y: ?5 b+ |- r
//Delete: 0.1GAS/time
; X- }- V$ p2 j3 Z8 ^: lStorage.Delete(Storage.CurrentContext, key);`
8 Z3 E" Z9 H; i& r. \在使用上面这几个方法时,有一些技巧:" H( b; h( F+ R1 U/ L5 Q+ I
1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。
5 @/ J( G4 T+ l3 J( D! E: N2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
$ ~- N3 k0 g; m8 t`byte[] orig = Storage.Get(Storage.CurrentContext, key);. y9 y- ?) ^% k0 Y; P" o
if (orig == value) return;//Don’t invoke Put if value is unchanged.
$ I$ s$ S5 }3 G& J6 mif (value.Length == 0){//Use Delete rather than Put if the new value is empty.! ?/ q  y5 W' C+ Y
Storage.Delete(Storage.CurrentContext, key);* M7 ^! w! b9 s& f- G8 Z& K
}$ t  _' o5 n4 S; F
else{% E9 Q% I6 X; }4 R
Storage.Put(Storage.CurrentContext, key, value);3 _7 }9 s" g3 B2 \, o" L
}`
% ?* k4 W0 c2 a7 F  N9 o4 q5 _, O1 [( q设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。$ z2 y2 P, b% y) N1 M! ?/ \
& g& t/ l  X# Q6 o9 J0 X( ~
BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i' m* C: w0 L) A$ B
随机性. F+ |' p$ j6 y& Y8 \( D
生成随机值对于智能合约来说是一项挑战。5 N! M( o# {' k# x/ e" K
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。* M- u$ W! [; h/ o5 v! l
对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。, W; c6 X; `/ U5 j6 n3 D
总结; Q  a* f& T' @/ k/ S) C
感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。
! V5 h/ F9 r$ r我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1