Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
72 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.% r, P) }: e' H2 F# k& q
类型转换
+ b" D3 A: A+ M! @- ], Z9 oNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。" H$ S+ h4 N2 p5 f  E0 u
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。
$ ?3 R( e: X7 J2 rByte[] to Boolean
7 q- R2 T, w4 O7 N& ^虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:# P1 C9 X8 l9 B( N% c, N
public static bool bybool (byte[] data) => data[0] != 0;" h+ B! S) {( {
然后可以得到如下结果:
2 \4 i* Y, ]* S3 h3 {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
+ R! u4 ~- s# i. ]& {- u. jByte[] to String9 G* @7 w' P' B( d$ `# M- [
这个转换直接由Neo.SmartContract.Framework.Helper提供
3 Q& h+ T7 @9 X8 m1 U8 i" ipublic static string BytesToByte(byte[] data) => data.AsString();4 h- \+ g, j. n1 ~0 N  g  R5 C
Byte[] to BigInteger
5 ^: b' R0 S- Rpublic static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
4 a# N3 s+ E, p: @7 GBoolean to Byte[]# ^' o8 V9 e; O9 Z; l
这个也需要手工转换。, ]( Q: I. @, P: w$ S
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});' v. R( r% o% z( M" S8 q  B& i
String to Byte[]2 D8 _/ }$ l* D
public static byte[] StringToByteArray(String str) => str.AsByteArray();. i1 S' V% j5 H
BigInteger to Byte[]$ K/ u8 h/ x# o; W3 F0 a
public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();6 E& L% d" F5 @- e; e
Byte to Byte[]1 y' b( W" l* ^0 p
你可能会认为下面这段代码看起来很好:. C1 h% Q" d( L% J! D% G3 H, l! f6 M: S
public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
1 w' s2 l# w8 ]4 b/ p  l) z: P  y它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。0 N% \# @% S* P5 `
操作符和关键字
( a9 \* g) I1 N+ X3 X1 Q3 F9 [9 K正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:. p8 |0 w+ H+ O4 S1 ~
Bool: AND, OR 和 NOT
( j6 R- y! M9 Q4 y支持操作符**“&&”,“||”** 和** “!”**
6 u4 c8 ^  o0 i. @* x* p5 Dbool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true9 ~0 Q; U$ Z1 B# I2 X
关键字: “ref” 和 “out”5 o4 \6 J3 \4 U; J" h) U
关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。* {6 f/ x3 p7 u; t. W  A4 M9 x8 b
关键字: “try-catch”, “throw”, “finally”
; k- S) d5 M9 }" U) E. i不支持这几个用于异常处理的关键字( A, _/ g) ~* w2 F/ p2 N
字节数组:级联和子数组# b% T7 y' ?- Z; O9 K" d$ R+ H
`//Concatenation# Y7 Z9 o. |( l" R& _
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
- ^0 w! T6 b0 L5 Q* [//Get Byte array’s subarray' M1 B" W& R" A7 W& f
public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`
& g: m5 T+ g& f5 Z关键字 参数中的“This”
) y; K  w9 \7 W1 a5 b有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。( s( l, F/ o0 P3 l, @+ e
// 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;    } }& x; E. a0 c1 l! C, f
使用上面的方法:
- p  s0 H  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());
) k: \: y* `/ Z- k8 I4 J- ]+ Z字节数组:修改值
( h3 W! d- i7 |7 x/ CNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。
7 B* m3 B: B0 h`public static class ByteArrayExts{
4 G6 m; r) y) k0 ^//… previous functions …
2 e1 q" E& H; j3 a! V! M/ |3 S6 rpublic static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
4 h" G, g9 U7 e8 f3 N/ n- [byte[] part1 = bytes.Sub(0,start);9 C2 X2 z; Y+ O; H
int endIndex = newBytes.Length + start;
9 S) {+ k) C! z: b( hif(endIndex 7 P9 p6 {/ A# k4 s  |
使用:0 \5 ~. E+ A3 K% c# `
`byte[] orig = new byte[5]{1,2,3,4,5};
$ P2 @" q& Z2 s. q* v  `( x; Dbyte[] newValue = new byte[2]{6,7};: A* T3 ]* [. O. w
//Replace the 3rd and 4th elements of orig byte array.# A3 l% b; B: t. T5 P" {) j
byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`
* V1 ~/ e6 c1 ?  b( E# Z) G; b存储2 x8 g5 Q# F5 t: y  h7 c6 N# V
Storage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:
& N  k5 S4 x- }`//Create and Update: 1GAS/KB
7 |; `5 V/ I8 `) M" r& h# oStorage.Put(Storage.CurrentContext, key, value);
/ i- v: n! u; g6 {: L& u$ Q. I//Read: 0.1GAS/time0 |9 p1 u4 [' z& n
Storage.Get(Storage.CurrentContext, key);5 U) H9 ^! h4 r) D; e6 q. x9 G( w/ n
//Delete: 0.1GAS/time! E8 y1 k3 c6 V) ?+ C
Storage.Delete(Storage.CurrentContext, key);`5 N/ s$ Z! @. ?" s0 E4 }- o3 u, L
在使用上面这几个方法时,有一些技巧:2 k2 M7 `8 b& A" e4 N
1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。
- S9 H- Y9 ?' q# Q! r/ d2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
/ t9 |! [5 i; i6 A* ^% h`byte[] orig = Storage.Get(Storage.CurrentContext, key);; _% V  E! O9 r7 s) @: H
if (orig == value) return;//Don’t invoke Put if value is unchanged.7 x( A9 i4 V9 {' l
if (value.Length == 0){//Use Delete rather than Put if the new value is empty.
; W4 K% c5 L7 i" i9 s$ zStorage.Delete(Storage.CurrentContext, key);
: u9 C  K+ v: F; E}
" z  \7 Y* `, v0 _# Felse{9 [' t( T$ w7 B2 x& @2 t* s& v
Storage.Put(Storage.CurrentContext, key, value);) \& D# N9 c/ u; m9 }. V& V
}`
4 d* i7 F+ @, r- o设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。  y- X1 O" P9 b: f* G$ s0 J
! M7 e$ G' t8 m, i7 D3 ~
BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i
& O: R% s9 z5 V9 Z随机性! g3 l  T' a2 f, G7 x+ a# H
生成随机值对于智能合约来说是一项挑战。" A) F$ Y6 ~: Q3 y, B" h* b
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。
) j; O: O5 \& L" e! p( d对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。
/ r. q7 C) o2 H. {# {2 f9 N) {总结' @) @7 J# m4 @, ^, h$ ?2 @* f' l
感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。
: A# R- V0 |$ S4 D3 I7 `" ~0 H1 w1 H6 M我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1