Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
67 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.
8 ~" w9 U* u! X* i$ u" l  H类型转换
+ O8 z& J, n- @& n. BNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。; g, [+ L& Q! }" ~7 e
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。
# [7 x7 U/ A/ N1 ^Byte[] to Boolean6 j2 n1 {6 F* A# M/ X1 D
虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:
5 a; y, {: f' U* ypublic static bool bybool (byte[] data) => data[0] != 0;
! A9 T4 a9 D) e5 X( z然后可以得到如下结果:/ l; |: @8 w, }+ ^) U
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
0 G+ q" @" C( q' |Byte[] to String# ]# @8 B5 u, O( Y2 f
这个转换直接由Neo.SmartContract.Framework.Helper提供
' Z* K5 f: `5 C6 r+ Z) opublic static string BytesToByte(byte[] data) => data.AsString();
! {5 {: ]4 I/ T4 g  C9 LByte[] to BigInteger  T/ z+ k5 q8 h, _& E; E$ g0 N
public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
. v6 Z$ a5 O0 B7 z! Y" fBoolean to Byte[]
! O3 o5 t. n- G" ?( E这个也需要手工转换。! [: y0 k2 q. O
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});9 B# G1 F* [. F7 Y2 r
String to Byte[]
  n5 v/ ]6 ^3 A  kpublic static byte[] StringToByteArray(String str) => str.AsByteArray();
2 s3 l' p6 s5 lBigInteger to Byte[], k3 ^8 P' c  G- D9 }" G3 m$ h
public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();
" m8 H  [+ ]' A# p- hByte to Byte[]: Z8 O( S9 n4 H" X9 n# X. E
你可能会认为下面这段代码看起来很好:
4 q( F: A8 Z. N  w# ?- wpublic static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
( k& H4 x+ E" R* v) h它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。
$ G0 L8 f9 g- j7 c: _6 q操作符和关键字
' B/ N' I( J. \2 H正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:: M% S4 ?% D- S1 m7 ]) c( L
Bool: AND, OR 和 NOT
7 w! c' o4 t- y  ^- I# K支持操作符**“&&”,“||”** 和** “!”**6 p  u% G( q0 A0 d7 }5 V- ?& t
bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true: Q, {/ j1 J& c! ?0 f
关键字: “ref” 和 “out”
4 T" H* E% P6 E  Y7 g. h& o$ a2 C关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。
% w# g/ w/ |$ u/ S关键字: “try-catch”, “throw”, “finally”
& C& r4 e9 ~- W& h5 S. C不支持这几个用于异常处理的关键字
& W% E  k0 H7 x字节数组:级联和子数组* o- X; W* p4 O
`//Concatenation
7 H( H: E: j- }; N! M3 B! q; Epublic static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
# b+ X5 s, N' d$ g' z//Get Byte array’s subarray
/ b2 F) b7 e4 {, S2 Y" _public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`
# J& ~/ |& P4 t  C- P关键字 参数中的“This”
1 N" d9 ?; q4 A6 p有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
  K( M$ M; D! O$ 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;    } }- r& _0 D7 ~; y+ B2 B
使用上面的方法:
- @0 c. T) q, t5 K9 g& pbyte[] 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());
3 A0 N# i1 e0 j* @4 V( e字节数组:修改值
' d5 ~# _, ?- m1 [3 iNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。
  B) Z& I0 [7 V4 G! {' B) J/ s" h`public static class ByteArrayExts{' Z4 I8 V, m) J" j4 p0 o
//… previous functions …* H7 p, a! y, g' C: j
public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){; p, ]" m; Z) j: e9 j( U* ~: M
byte[] part1 = bytes.Sub(0,start);, i. x# d8 L! y) F
int endIndex = newBytes.Length + start;
4 @/ r* B) A$ E2 wif(endIndex
; O: ~: E  b3 u2 \, I使用:" J0 j" a3 K: s* h1 D: D
`byte[] orig = new byte[5]{1,2,3,4,5};5 W4 i0 f2 U: b. j8 w7 ?  j
byte[] newValue = new byte[2]{6,7};( W- N1 \8 `, |" S* D
//Replace the 3rd and 4th elements of orig byte array.
: n* X$ E( H0 o$ d- I  vbyte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`- @; m2 Q, D. _/ w
存储
& K$ X2 S1 s# G. L9 j# XStorage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:
  f, d; h$ k( l' x% F/ ]`//Create and Update: 1GAS/KB
) g# F* L2 t9 p$ W4 u- lStorage.Put(Storage.CurrentContext, key, value);
# y; v. e: b' |5 d1 ]0 E% J//Read: 0.1GAS/time0 T5 y6 O3 |5 C% \1 K8 K
Storage.Get(Storage.CurrentContext, key);
9 @- A& F; R& v, O//Delete: 0.1GAS/time
- \, E8 s; S5 P6 o% F! x7 wStorage.Delete(Storage.CurrentContext, key);`
+ s7 c/ x3 l8 c, D3 S在使用上面这几个方法时,有一些技巧:
. J9 n! l. `; K4 j# B6 D1 v/ U1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。0 {2 t+ W. I1 [6 J$ @
2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
0 T) F' J' s5 l6 P+ n, s`byte[] orig = Storage.Get(Storage.CurrentContext, key);
3 ~: i3 x3 e0 K. a" N; I7 |5 qif (orig == value) return;//Don’t invoke Put if value is unchanged.
, Q, e1 y% j5 R: s6 a7 X/ Nif (value.Length == 0){//Use Delete rather than Put if the new value is empty.# E" f) V) q  m) k
Storage.Delete(Storage.CurrentContext, key);4 y+ z) ?, L; {- Q* h
}
0 w: |4 C7 j9 e3 Q+ D5 I  |! @else{
; l$ L$ D& T" u" k- b" `Storage.Put(Storage.CurrentContext, key, value);/ E' L, Q3 C3 i, Q: Q% D
}`' [( |; V8 R; r' p: B* Y+ L
设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。! x' U! ~/ n' V

9 b5 \! o) A5 H3 ^) ]; E/ ]BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i1 Y0 \/ G! i  s" V9 ], }& x+ g/ ]
随机性
& f$ u) o3 \- G5 @. k生成随机值对于智能合约来说是一项挑战。! ^$ D/ r0 k3 n$ @1 D7 c# W# r
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。( i( @/ F" Y8 s4 e  T" s
对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。' s  x$ R3 p- [. z* t* |2 o
总结
* a$ Y3 k2 E/ [5 c6 q' Y7 U" s! @; O感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。
! }8 O. y0 ?: s' Y我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1