Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
92 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.- X. o( Y7 r/ e/ |1 B% Y
类型转换
' \% W6 U: M4 k3 L! sNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。% ?% e$ h; @2 l0 I8 y, A
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。
: _% P: i( X; A" S% T' n2 EByte[] to Boolean
) o9 E% t: C6 P; f虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:, M0 A" F1 T* n) o) z' C( N
public static bool bybool (byte[] data) => data[0] != 0;
$ S: Q" g6 }, j- [( U3 ~0 p然后可以得到如下结果:
1 G- @1 c* w/ c) S# O" rbool 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 ; ~7 A9 r& e- v- [/ ?/ i* {
Byte[] to String
( f  z- ~7 D8 d" ^( e! I( v) |这个转换直接由Neo.SmartContract.Framework.Helper提供  [2 w2 a" a" q0 T& Y' X
public static string BytesToByte(byte[] data) => data.AsString();5 A( z% A  v! @
Byte[] to BigInteger, c2 R$ O2 n1 J9 x  }4 Q" _8 U/ O
public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
$ ]7 @# n& }2 N6 n: J9 H* {2 ABoolean to Byte[]
8 R' }- H: V/ N+ H! R这个也需要手工转换。! j5 B% I# T+ v- r+ o
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});& G3 |: E+ K4 w/ s" d& r6 M
String to Byte[]
# a0 H2 i+ e+ W/ Q1 u. u3 _* Opublic static byte[] StringToByteArray(String str) => str.AsByteArray();& {2 g5 k5 a6 j, ^
BigInteger to Byte[]
' M  P7 J2 I- B/ A. z. \1 [public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();" u$ F: |" G$ E2 ]" H
Byte to Byte[]/ [; B0 T  X  A+ e5 @% [# z3 \
你可能会认为下面这段代码看起来很好:
$ ]7 K2 u! v) G, L) epublic static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!: O, J8 E, y/ T( I2 d2 G0 _
它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。3 ]* ~$ ]3 ]/ d3 ]1 j& l
操作符和关键字% b" g; R; n3 C. S7 e
正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:: W( J0 T/ l' \6 `1 T
Bool: AND, OR 和 NOT( a% ]8 a1 R' C" y9 m) n
支持操作符**“&&”,“||”** 和** “!”**
! o% k! h' K+ Z' u) [bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true: i. M1 i* A4 s3 L! i" _
关键字: “ref” 和 “out”
' |$ O% x, G  U9 V3 X( `5 B关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。: _; I6 W9 X! I7 R* e
关键字: “try-catch”, “throw”, “finally”7 k' v$ n; A. q( ^2 l
不支持这几个用于异常处理的关键字
; i5 P) C% }9 f$ d& O2 {2 H字节数组:级联和子数组, P3 b9 z' @1 k8 e* e1 k/ P
`//Concatenation9 B( U8 g1 L' T9 w% y& ]7 a' U
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
2 v7 \9 q4 e0 ?; M//Get Byte array’s subarray
' ^* \5 J, I( a$ U( y; spublic static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`, i, R  z! E/ G9 C( l
关键字 参数中的“This”
  G1 X6 N0 F" b, `0 R  O有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。& k: H( O$ A5 }3 Z
// 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;    } }' V4 m8 R+ M, V% a% ~
使用上面的方法:" _# k4 O5 Q( F! g$ k: I3 s$ U
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());
- f+ H* Y) y4 k2 b3 n: {0 ~字节数组:修改值
+ {6 k* n2 q+ t. |4 e9 \NeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。% [% n& z: h7 i8 B/ {
`public static class ByteArrayExts{# W& Q& T5 c' P: F* d4 j& Z
//… previous functions …
5 N& l2 V1 {8 m) X2 Qpublic static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
% ]! M! j: }; C4 c2 Qbyte[] part1 = bytes.Sub(0,start);
. u+ @7 S4 w" ^+ \' x4 _int endIndex = newBytes.Length + start;& y" f$ q6 ~  d- \
if(endIndex 2 S7 B( j+ C  o5 j2 A5 c
使用:" r. c) m: S7 ~& x0 I$ J# o, N. }
`byte[] orig = new byte[5]{1,2,3,4,5};
. N: n1 R- ^/ ?$ e, ubyte[] newValue = new byte[2]{6,7};
. M7 Z4 k4 I7 O9 G& \3 p+ Q( R//Replace the 3rd and 4th elements of orig byte array.
! B0 }6 C: z+ j! O: Ebyte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`
8 f6 R9 z3 [/ J; L存储
5 |' A( N5 Q; _  c# V4 MStorage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:' M0 f! L2 [- F0 ~( c- U( T! h0 }
`//Create and Update: 1GAS/KB
. i; z2 f) q3 U/ O) E4 V8 tStorage.Put(Storage.CurrentContext, key, value);- T3 u- n6 \( s
//Read: 0.1GAS/time
: C, S, b( P& U) ?3 M7 wStorage.Get(Storage.CurrentContext, key);3 d( g2 H# m. Z0 i/ ?" F1 B0 l; f
//Delete: 0.1GAS/time
! {; J6 h8 |3 @% Z' x+ Z5 VStorage.Delete(Storage.CurrentContext, key);`
% x8 s8 b9 A" A( S: X在使用上面这几个方法时,有一些技巧:+ [( K8 w) a$ U
1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。
+ `/ e/ `, W! r2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
" f9 b' r; j1 U" @* w" Y! o`byte[] orig = Storage.Get(Storage.CurrentContext, key);
8 v9 W$ D% Z+ t  uif (orig == value) return;//Don’t invoke Put if value is unchanged./ K! m8 K4 Z% Z, y( Z/ Z0 f! K3 |
if (value.Length == 0){//Use Delete rather than Put if the new value is empty., y, g! }4 v( @# O! y) ?% Z' u
Storage.Delete(Storage.CurrentContext, key);6 }+ O) o9 C9 a+ p( u; t
}$ n2 g# R  U% r6 [: J
else{
" E( D; {9 D: b8 C: g4 }2 `Storage.Put(Storage.CurrentContext, key, value);
/ H; T) N* m4 y; X+ O  N. @; H}`
7 T: D, o2 Z. N& y1 q' F  p设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。
3 k2 ]& V- s* Y7 f  Z; E# I8 q* L# I4 p/ ^0 m5 ^' r9 Y
BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i
! j* p. o! h7 p4 f随机性& h8 r$ h3 E, D" l8 D& z
生成随机值对于智能合约来说是一项挑战。1 c6 g2 \. D* P' S! S6 ?! M: H
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。
8 L8 k: U' r3 d0 ~( r7 d5 o对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。  j) ~1 f6 f+ N) F8 X  V( c( }. p& g
总结0 u2 W/ D0 c, @/ V
感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。4 ]4 u2 Z; P+ @$ L- a6 k
我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1