Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
81 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.
7 S2 _5 }3 F2 `0 z类型转换# x8 x6 f# o) K3 ~: i- X" y
NeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。4 Y9 n, a- G  L$ Z: N
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。
, u; J+ K( h. C  k% c9 S7 W8 i& dByte[] to Boolean9 O# O7 B9 T1 ?; K! ^8 s2 m8 l, ^
虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:- I' C9 U  y' N5 ?. a) @
public static bool bybool (byte[] data) => data[0] != 0;
( W, y; D. c$ H8 D然后可以得到如下结果:3 i5 r4 O' y8 e8 s4 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
. L4 F* `; o% H% Q+ |' A7 zByte[] to String
# W7 p4 O# E9 I: R& {* y5 |这个转换直接由Neo.SmartContract.Framework.Helper提供
7 v1 C* y1 P6 W+ i8 wpublic static string BytesToByte(byte[] data) => data.AsString();# S: o( h; t% R' y/ @
Byte[] to BigInteger
1 [+ p6 n2 r# p9 ppublic static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();& ^1 h0 ~6 \: ~1 f
Boolean to Byte[]
4 J) I9 e* p0 X4 x) H# @) m这个也需要手工转换。
$ R9 i& x# X. u6 Q! a# m3 wpublic static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});
) Y& ~7 {* |7 `; Y2 Y1 g) m* a" f" HString to Byte[]
$ F# J( J- X. D, \4 Bpublic static byte[] StringToByteArray(String str) => str.AsByteArray();
6 U& _& R* ?3 h6 M0 v8 A0 {5 FBigInteger to Byte[]
  o% j0 `( |2 \/ L( {public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();( B9 N6 m! i+ b
Byte to Byte[]
  u, `2 ?) J% E, r' [你可能会认为下面这段代码看起来很好:9 _; Z8 f6 t0 @
public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!9 @/ X5 i2 g5 I4 j: R. f
它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。
% h0 l# V5 D( m# t操作符和关键字' ?6 H& @+ f8 |8 Q8 W- ^/ J
正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:6 M$ }1 I6 p# S# J9 ^
Bool: AND, OR 和 NOT9 F5 x9 x, j1 M5 R3 b. [( E
支持操作符**“&&”,“||”** 和** “!”**  @6 D, U; [! f4 E$ c: \
bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true/ n" q: m/ D. s' |2 `; T
关键字: “ref” 和 “out”
! f! C7 ~+ E0 T0 F# N0 x4 Q* D' O" `  z关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。
8 J7 V" j6 S; O. \/ ]! |$ `关键字: “try-catch”, “throw”, “finally”5 S. p5 m" g; T! e! T
不支持这几个用于异常处理的关键字/ N# p" L- t/ N( B$ [/ D
字节数组:级联和子数组+ y4 k: v. s2 P
`//Concatenation  X; c3 e$ q" E8 k( v6 k
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
5 ~6 D4 r$ _" \! J- ?  e//Get Byte array’s subarray
- G+ D0 N7 Q1 O* Y+ Upublic static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`1 k) @, ~$ \! B; r" D
关键字 参数中的“This”
6 X/ o( v4 S/ C2 G" y有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
9 E7 _# h' i# T* [: E- s// 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;    } }
' P2 [3 E7 i8 ^( [5 a: ?7 ~0 s) O使用上面的方法:+ B! E& d- f+ j- n# ]0 `, H. _
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());
* `2 ~6 F* d* D) ?! h# t( P: E字节数组:修改值
- ^4 }4 m7 I$ e2 wNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。5 T8 ~, Q7 ~( O3 H% p: L) g* q. y
`public static class ByteArrayExts{5 |1 o: O' T8 w; m4 M
//… previous functions …
  I2 T; N9 z8 O% \public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
" z$ s5 {4 A6 O. u8 Nbyte[] part1 = bytes.Sub(0,start);
7 a3 [4 c6 x  Q8 Bint endIndex = newBytes.Length + start;
7 p, Y) @: |; w! Z. W8 Bif(endIndex # d+ o) A. V0 [6 Q1 A! L  w
使用:
& K8 S7 ]  V+ {; {5 I. x; D, s`byte[] orig = new byte[5]{1,2,3,4,5};
2 S7 s! U% k) z1 mbyte[] newValue = new byte[2]{6,7};0 b, j( B! a$ _- ~$ Y
//Replace the 3rd and 4th elements of orig byte array.; v3 A, o$ Q1 V' ?8 [5 q# s0 B+ r
byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`
' H& A) r+ C" {$ Q存储
8 w% b$ [0 l, b0 A  s3 g# n- {0 m& oStorage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:" o& E3 y" z, y, l8 X- q' R; l' ]7 H( c
`//Create and Update: 1GAS/KB: _) Q0 G" u+ [6 X8 `( m
Storage.Put(Storage.CurrentContext, key, value);
- x. ~8 I; q7 ~7 {7 Z' J//Read: 0.1GAS/time5 c" P0 l  d' [: Q
Storage.Get(Storage.CurrentContext, key);9 S$ ~- c- k1 U. M
//Delete: 0.1GAS/time7 }# V, d  W6 [" R  _
Storage.Delete(Storage.CurrentContext, key);`
5 c+ i# P! C$ l在使用上面这几个方法时,有一些技巧:
0 s) n( B0 J  J& z1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。
0 E: L  x2 f  a5 l% q, N6 K1 z0 p2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。, J( s2 J! t1 x) b8 |! \" e
`byte[] orig = Storage.Get(Storage.CurrentContext, key);/ n3 L6 v: S  k% W, @+ I2 D
if (orig == value) return;//Don’t invoke Put if value is unchanged.; x' R! n* t5 q) q/ ~% p0 h2 F6 f
if (value.Length == 0){//Use Delete rather than Put if the new value is empty.! x; e* V  M, s) h- s
Storage.Delete(Storage.CurrentContext, key);
4 n! y. ?4 S5 Z% k; c}
+ n" I' j- w0 Melse{0 E7 w  s0 w5 `- S4 V; ]
Storage.Put(Storage.CurrentContext, key, value);3 B; b1 G7 Z1 s9 \7 J
}`
# T! y/ w9 R7 o- g* m设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。" B. z4 K% j. T, L

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

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1