Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
71 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.0 ?. C& ?0 K: E2 o3 K3 Y8 z  s% H
类型转换; c/ N6 G4 V5 ~' B
NeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。/ U, x6 J" ~6 U0 R
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。1 b" l8 q2 m- y# [- H* |3 I4 `7 @
Byte[] to Boolean
. {' L3 e* f: A4 T, J* Y虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:* X% ~* h1 [. e% k
public static bool bybool (byte[] data) => data[0] != 0;
5 z" L0 Y$ R, |; d然后可以得到如下结果:5 }5 w- {0 H+ C/ @
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
9 a. \# a. a4 b, T: z0 U3 o. cByte[] to String
- x' O4 T$ {6 R这个转换直接由Neo.SmartContract.Framework.Helper提供
  I- I- ^+ `4 K" x& @1 V' @public static string BytesToByte(byte[] data) => data.AsString();: k6 W' a( i; m: n# k% k) G
Byte[] to BigInteger& E+ q$ {( D4 Q
public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
1 u) K* u) P! ^Boolean to Byte[]
% L9 g: ]* E* r% R8 M这个也需要手工转换。
- ~: F2 B6 I; Apublic static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});
  H5 S. t4 F( ?! H* ]4 `7 {: YString to Byte[]  u7 K" d, \. f+ i" T. }
public static byte[] StringToByteArray(String str) => str.AsByteArray();
0 v/ M. C9 e# `' T  K, hBigInteger to Byte[]
. Z9 m" m9 l. t8 ^public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();
7 k- O0 P; y8 s% Y% jByte to Byte[]+ G5 H4 W0 F: M. E  f
你可能会认为下面这段代码看起来很好:
* z. L9 m' X3 R: v+ z& n, |8 g  bpublic static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
1 S5 {* b. f/ M* D* p8 X7 o它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。' Z: g3 _* s' [! h
操作符和关键字4 _- H2 X( n; E  t' A: }. c
正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:
: q; F, f( j: ^0 S5 u2 _Bool: AND, OR 和 NOT
, Y2 l& L1 p3 l支持操作符**“&&”,“||”** 和** “!”**; R* _7 c  U. D' n
bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true- u- k% G: J3 B+ k3 w
关键字: “ref” 和 “out”8 ?8 L2 ?: `: ]1 i( w& `$ \  m3 _
关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。
2 t% G* K1 \" B关键字: “try-catch”, “throw”, “finally”
6 m' {. m! i; j不支持这几个用于异常处理的关键字
0 f$ y4 s) T& ~7 L" u: E字节数组:级联和子数组
+ t2 P1 S' t( u`//Concatenation
; a6 |8 z& Q+ r2 Q0 ?  tpublic static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
9 \$ A% i& u% V3 ^) g3 I5 _' \//Get Byte array’s subarray
( G) c3 ~+ [- vpublic static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`
) b- J5 F) U; G% i8 j关键字 参数中的“This”5 _8 @& S  c9 T
有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
7 u0 g4 f1 c" ]// 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;    } }
5 l$ ~; m8 R' `/ R. v0 k使用上面的方法:" D( w! s, H/ u; F
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());0 f( L1 C9 ?; s% M- I  ?
字节数组:修改值
/ B8 I1 H' p% v! N6 l1 zNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。
# j+ ^, O9 f' O9 w`public static class ByteArrayExts{8 k  H& ~8 n% P! p/ @
//… previous functions …
" c- P4 N7 @' @+ k7 f* z; Hpublic static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){9 H' S, K3 Y: B7 f% f! u$ D5 ~
byte[] part1 = bytes.Sub(0,start);
9 v7 T/ ]/ k4 v4 l0 oint endIndex = newBytes.Length + start;5 Y" m' c& ~3 K4 r
if(endIndex
) r9 R' Z+ P9 ?' X9 p使用:
! h7 B- p) J) ~2 p1 E, G/ R`byte[] orig = new byte[5]{1,2,3,4,5};
& g2 l( K' x6 k# O% ~1 q0 Fbyte[] newValue = new byte[2]{6,7};7 J$ ^1 v9 [8 a) a0 f/ s
//Replace the 3rd and 4th elements of orig byte array.1 B7 M1 P( M& l  l  o
byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`$ G, s2 }* n3 q+ n. q; {) m, V4 e
存储
2 e8 f5 E2 K' Q7 U; y3 DStorage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:0 W1 b3 b0 J& L- A. i9 C; y  G- L
`//Create and Update: 1GAS/KB
* ]; ~4 J: ]! m4 fStorage.Put(Storage.CurrentContext, key, value);
  l& Q: K( k+ O5 n5 Q) f* U//Read: 0.1GAS/time
3 e3 e& v8 N8 }Storage.Get(Storage.CurrentContext, key);$ h$ }9 V. A) d8 d4 e
//Delete: 0.1GAS/time& O) B. `9 X0 f7 t7 n
Storage.Delete(Storage.CurrentContext, key);`
6 W1 c: O2 @: l' A) b! `在使用上面这几个方法时,有一些技巧:, {0 }# i% _% t7 U  }! V
1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。3 D9 e6 C2 J# A' z8 x
2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
, a$ j; w0 b9 f" [, e6 H`byte[] orig = Storage.Get(Storage.CurrentContext, key);
; s) K6 u% s2 O! K7 O7 \4 F" e1 e7 Uif (orig == value) return;//Don’t invoke Put if value is unchanged.3 |( U' U$ |. L
if (value.Length == 0){//Use Delete rather than Put if the new value is empty.  ]8 W: g' n2 {0 C4 e3 H
Storage.Delete(Storage.CurrentContext, key);
8 L+ f; D' c) _}
: m5 y  b4 E/ H0 uelse{5 x8 G. g, q+ Q2 S5 _5 u+ ~
Storage.Put(Storage.CurrentContext, key, value);
4 K: J3 q/ B  H/ L  C}`
6 V7 M$ l: `+ O2 ?" w设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。/ A) i4 C5 l$ i7 y

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

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1