Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
70 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.1 z/ k" ~1 N! A8 i* o! k+ T" `
类型转换
; q. h" i1 i: ], h7 kNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。  `& E7 o- X4 c1 j+ `
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。5 `: X2 l2 n  T: B' t; `+ o
Byte[] to Boolean2 m5 U& A+ `: {3 k. H* J' K
虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:
  o1 C- V# {9 v# Ypublic static bool bybool (byte[] data) => data[0] != 0;' p8 K- `& F. Q$ U9 |  T1 |! c
然后可以得到如下结果:2 F) @3 ]. P4 ]6 G  y
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 ; A/ U& U. b5 ^1 Q# T5 C1 }; u
Byte[] to String! S) x8 J* n+ l5 v; m
这个转换直接由Neo.SmartContract.Framework.Helper提供( J) D/ b3 M6 G7 g% ?# _  a4 F
public static string BytesToByte(byte[] data) => data.AsString();$ l  ~% f9 i. A' @
Byte[] to BigInteger
2 U, u2 m/ X) qpublic static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();: j; Q. c8 ]2 S$ P
Boolean to Byte[]
8 R, M9 f: p: q% _这个也需要手工转换。; K3 e: u) }3 w- `: j! d
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});# e6 a% m$ l1 m- L. T3 c
String to Byte[]# o6 n7 j0 C8 p; N
public static byte[] StringToByteArray(String str) => str.AsByteArray();  N. ]% \3 z) P
BigInteger to Byte[]$ |( Q2 P; ]. `/ {' x
public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();
9 d, R/ M: F, [: P. r) M, cByte to Byte[]
5 q" @" _: K! B/ B1 t/ i4 ^( e你可能会认为下面这段代码看起来很好:2 [- Q5 O4 w2 E  j
public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
) t! G8 B! P, Y) V4 ?( h. T! I它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。
. V" B3 _5 k6 L' z/ B操作符和关键字
1 Y8 r1 Q" ?# `* ^  Z- {7 M0 g3 C正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:
( n8 X2 ~. j! V1 k* q; Q( q: T% `Bool: AND, OR 和 NOT+ _% q) _+ G( m" p: b8 x
支持操作符**“&&”,“||”** 和** “!”**1 D) l' a5 C3 \$ y- \
bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true
0 o% |+ n8 X: S! V9 r; \; B' ~关键字: “ref” 和 “out”4 E# d8 Y7 {5 Z3 x) n) D% S
关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。1 R9 [$ n4 D* o
关键字: “try-catch”, “throw”, “finally”* c: J) T0 i3 A% d/ O' o
不支持这几个用于异常处理的关键字5 V/ G0 ]# y6 b; p. Y" s2 F2 j
字节数组:级联和子数组4 l# i5 ?  s8 c5 F: O. {
`//Concatenation
5 |; H5 j3 T! |$ \/ hpublic static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);, {; N4 N, Y: ~; q( D
//Get Byte array’s subarray" _* k0 y7 K, J$ F( L& H
public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`0 d) K) z% F9 N5 i
关键字 参数中的“This”) Y- b4 T- B7 D
有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。5 @- Y8 p# n9 n6 i& r$ _0 k- v
// 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;    } }
  L* a9 o# f( L! A' Y; p使用上面的方法:; @6 s1 |" a# m; a: N# n/ Q
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());
6 o! _: _) G. i# K1 i$ E4 [2 K& i字节数组:修改值
, a0 X7 {: T( Y4 j5 l, HNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。) i1 ^* ^  x$ M" w8 _* P
`public static class ByteArrayExts{
! R1 U. D% c3 U/ q: I$ ]0 S//… previous functions …
2 n3 m7 E9 u$ a0 jpublic static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
. i$ Y6 m+ M8 ?, i7 tbyte[] part1 = bytes.Sub(0,start);
+ i+ V+ i5 c, C2 U% Rint endIndex = newBytes.Length + start;/ B7 U4 D# `! T$ l! k
if(endIndex 8 r# M' b' x8 T% p3 p! d( ]
使用:0 h$ g5 A6 U3 A: [) ]; }+ Q1 e
`byte[] orig = new byte[5]{1,2,3,4,5};7 S" h0 p0 y$ R$ }
byte[] newValue = new byte[2]{6,7};0 H% w5 T( J9 B3 [
//Replace the 3rd and 4th elements of orig byte array.
/ u( A3 ?) ]7 D7 f  Hbyte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`2 |. c" w5 D) Z* k: s; ]
存储1 c  l) y4 b0 \% X4 X
Storage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:+ V( P+ M: h' N2 ~, f+ r
`//Create and Update: 1GAS/KB) N: m# S% h( o2 h& q
Storage.Put(Storage.CurrentContext, key, value);! p7 J- K. K- m3 D
//Read: 0.1GAS/time& l) ^: b) p+ B/ ?, B7 X" j8 _# t/ D. k  P
Storage.Get(Storage.CurrentContext, key);0 P% ^0 B/ ~2 E0 d/ D% ?/ c: J) i
//Delete: 0.1GAS/time7 G: o5 k( f' q" f6 @  c
Storage.Delete(Storage.CurrentContext, key);`% p5 X4 t; A( G+ f0 r' `; \( Q
在使用上面这几个方法时,有一些技巧:
5 `/ [, ~2 I6 w8 D# U* ^1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。8 }; {0 \, u  P5 c) R
2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。# s9 M* Z) d2 C( W( ]' H, V+ u
`byte[] orig = Storage.Get(Storage.CurrentContext, key);
! F; h) W% B' A1 i, N* T0 r5 \$ Nif (orig == value) return;//Don’t invoke Put if value is unchanged.
3 _. Y$ `( d6 ~" r. G3 Wif (value.Length == 0){//Use Delete rather than Put if the new value is empty.
# K' Y* |, B4 ~, f. MStorage.Delete(Storage.CurrentContext, key);
, z+ Q( m) p- ^* X2 \, |}
. Z2 N  g3 N0 @& Oelse{
) y2 W, U' V+ H1 N) ~* i8 AStorage.Put(Storage.CurrentContext, key, value);
" G3 J9 W( P1 _! y; A  b; m2 x3 Z}`
5 W, a$ D  T0 x, p, y  U设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。
$ ^# y1 L" R: h5 _- X; j6 S& R9 m0 a5 S& u# p
BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i
7 z- A; ?- M" x9 a' m. S9 z随机性0 j& Z& X2 M* @; k' o
生成随机值对于智能合约来说是一项挑战。% C/ Q6 r$ m/ u. Y7 j" Q
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。! t3 H( X: I0 ^! @
对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。, q3 A( i9 v' r3 l
总结
2 g4 L, L* X) V0 a6 ?5 n感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。: P6 l6 L9 [( w) E
我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1