Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
68 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.
/ P' J  k, N1 f: p类型转换
  C7 B1 T5 ^+ @2 ^/ Y+ i2 s3 kNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。1 {  `" H7 z( a' ]1 _/ `
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。8 X. h; Q, h' r% f0 E% l
Byte[] to Boolean4 F4 C3 D" m* G: i: T
虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:
7 K( U* P7 T! R4 @5 @  Xpublic static bool bybool (byte[] data) => data[0] != 0;" u" N. g( W* \0 D9 M6 `4 G1 E
然后可以得到如下结果:
) {; d; F/ ~9 U. M0 [9 lbool 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
% ^2 w8 f4 I3 aByte[] to String3 r# M+ m+ f% |( A+ x
这个转换直接由Neo.SmartContract.Framework.Helper提供
+ Z# g9 k3 S# ~9 f1 [+ ^( z/ x/ \  u& Kpublic static string BytesToByte(byte[] data) => data.AsString();
0 j8 @4 T2 j+ n1 ?Byte[] to BigInteger
9 y" d, b) d4 Fpublic static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();/ [  F* Y- C' |
Boolean to Byte[]
: [: H. O- H" g; d% j6 J7 n. {6 E+ U这个也需要手工转换。* @2 |7 e- I) I) m- h) z* t
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});$ d6 V' W/ U3 j# a) ]" Y9 v
String to Byte[]
, M+ _) C; U& y; s9 i; Ypublic static byte[] StringToByteArray(String str) => str.AsByteArray();
) Y7 y8 y7 u4 r; ]+ e, t& fBigInteger to Byte[]
* P! O" w5 g; m) Fpublic static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();9 F! p1 [& G$ I/ Q. _7 m5 p7 T
Byte to Byte[]
+ T4 w% ]$ c% G( o7 ]% F) k1 b; H你可能会认为下面这段代码看起来很好:
! @' S( Z$ U6 a& wpublic static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
) j8 [  c& Y0 @4 w8 {$ k; Z& Q% E它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。! ~" V/ c+ z  F, C# g2 d3 I
操作符和关键字9 z4 N1 j5 k. h$ t; M, R5 y
正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:+ W& y% r% C+ C- D
Bool: AND, OR 和 NOT2 I  e$ S6 H/ |  z0 _6 Z3 h
支持操作符**“&&”,“||”** 和** “!”**
# k, G4 _$ ^2 N9 E8 T9 c6 @bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true
1 p; j- S& b& P* z4 z5 L关键字: “ref” 和 “out”
/ `3 s% K9 P" Y) h- o+ z关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。+ w4 M5 P" j! E! @
关键字: “try-catch”, “throw”, “finally”9 g/ \. @' N* _, I
不支持这几个用于异常处理的关键字
( q( N! n9 s+ m6 N- l4 I8 O字节数组:级联和子数组
: w7 F" b; @% y) v/ M6 c`//Concatenation
6 H( }) [$ {4 t. wpublic static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);# X: K4 q" P8 ?* O/ e7 T
//Get Byte array’s subarray
1 _6 Z5 Y/ o  S3 ^0 \3 \3 ]0 p! l- i0 Rpublic static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`0 G* S/ N! i" ]( k5 T6 T9 m
关键字 参数中的“This”5 I2 M- e3 e& I
有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
; K0 a0 i; ~* }// 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;    } }. u" C6 E0 B" f, `& y
使用上面的方法:
) S) U6 ~. y6 C# lbyte[] 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());6 `: U4 _" h7 k' j4 U- F( W' N
字节数组:修改值
; g# w/ G: ]9 O% qNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。
4 X2 ?- |- d6 \7 L! Q8 _`public static class ByteArrayExts{
4 _% R& T5 |( Q2 g$ ?$ ]8 `//… previous functions …+ g$ }/ Y3 Y4 A5 w
public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
& h" B9 c/ T; a" Z; F2 t8 Qbyte[] part1 = bytes.Sub(0,start);* |: U8 D" ~' Q4 N9 r8 P/ m
int endIndex = newBytes.Length + start;
$ {1 k7 F4 F- a- K* K$ dif(endIndex
5 ~' N+ E; @. n( k使用:
/ q9 h" B, r" ]`byte[] orig = new byte[5]{1,2,3,4,5};4 r- k: L) |; f7 R- U
byte[] newValue = new byte[2]{6,7};6 m2 k. p6 u) z. U4 g( p
//Replace the 3rd and 4th elements of orig byte array.
( ?* m' a% B0 U: V( Kbyte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`" L' _3 d3 \& E; e
存储: ?6 v0 ~& i' R: q( ]2 `
Storage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:! `# m: Z) g" Y& u
`//Create and Update: 1GAS/KB$ I; ~9 B7 \4 O+ K6 c6 D' n( D
Storage.Put(Storage.CurrentContext, key, value);
1 j2 ]5 Q/ r: _//Read: 0.1GAS/time
5 g, f% z2 y4 ^2 DStorage.Get(Storage.CurrentContext, key);
, Q, j1 @3 J  n; u/ f" I//Delete: 0.1GAS/time# Q8 j# Z7 l# y) b. I) ^- N
Storage.Delete(Storage.CurrentContext, key);`2 N/ m3 F/ R9 }! O
在使用上面这几个方法时,有一些技巧:
( Z# j& ?- K% D& g$ b6 q( ^: Y1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。- C: |4 e9 @+ U
2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
1 Q9 l1 N8 s% {`byte[] orig = Storage.Get(Storage.CurrentContext, key);
# A" c( C" ~7 wif (orig == value) return;//Don’t invoke Put if value is unchanged.( ?, N; {! F; N6 n2 i
if (value.Length == 0){//Use Delete rather than Put if the new value is empty.# g1 }3 p$ `7 _7 m
Storage.Delete(Storage.CurrentContext, key);1 C- P' s6 n+ t2 y# q" `& y
}& y9 @% o) t) ?0 N8 y9 H2 r& c/ W
else{: U9 B) L; F/ ?% x; f* ]& O; ~6 `  x
Storage.Put(Storage.CurrentContext, key, value);, {/ _5 x+ {% `8 Q- [
}`
8 e( b2 ~- n$ R设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。
3 U7 b" h; Y4 D9 m+ v1 P
" R- x& M4 {/ T7 Q/ OBigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i
4 J% Y. D: N3 X" t: _- e$ ]5 Q随机性
! T' P( G8 |) v" n; D' f( M生成随机值对于智能合约来说是一项挑战。2 W5 U2 S* s, x4 `3 e4 u9 z: K" }
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。
8 J' H  g( X. w1 H6 \7 w9 z$ D对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。+ n6 S0 m3 G: }1 x/ z% W2 d7 H9 ?
总结
- l) f7 n1 v- `感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。
: F, D5 n* r( C: \; M我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1