Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

一点评谱
110 0 0
开发C#NEO智能合约的最大挑战之一是NeoVM支持的语言特性,实际操作中使用的特性比官方文档提供的要多。 还有一些关于存储交互与随机生成的实用技巧。 Enjoy hacking.
, N0 o7 r  @' W/ z# C( z$ m% k类型转换
/ o* J4 O3 k+ p: }) G  rNeoVM支持的基本类型是字节数组(Byte []),然后是常用的Boolean,String和BigInteger。 还有其他整数类型,如Int8,Int64,UInt16,long,ulong等,这些可以被隐式转换为BigInteger。 Float类型不受支持。& T# ]5 C, w2 B. o+ o- S( q& Y
所以我们只要关注Byte [],Boolean,String和BigInteger之间的转换。 注意:有些转换不是官方定义的,在这种情况下,我会尝试做出最合理的实现。0 `. r* S( v4 U! c# h( M6 b
Byte[] to Boolean
- w- k+ Y6 S0 E& Z5 N虽然这个看起来是最简单的,但实际上它没有直接转换。官方说明中只提到False等于整数 0。我们假设True等于所有其他值,而空字节数组等于False。所以我们定义了以下几个函数:
8 ?- C. W8 B1 o* S! p5 ?# g- @/ hpublic static bool bybool (byte[] data) => data[0] != 0;
& ]9 `/ `& N3 P! u. |3 K然后可以得到如下结果:
% Z. z& K9 U; U: jbool 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
6 N2 D& `+ G. c0 A" ~2 _Byte[] to String
1 C5 \" `4 i" E$ s8 C- T3 n, i这个转换直接由Neo.SmartContract.Framework.Helper提供% w. L9 q) ~0 u( r
public static string BytesToByte(byte[] data) => data.AsString();, B$ @% z9 W" E8 g( g5 E0 q4 d0 F
Byte[] to BigInteger
* f/ q; o& z( _$ z1 \8 wpublic static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();7 N* \9 X- o$ i) S
Boolean to Byte[]$ u5 ^2 `' ~; u; f- E8 c4 c
这个也需要手工转换。
8 m. i4 _" ~! Y+ o: ^public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});
% o* n5 V7 u, R0 Q# g% V. @) E0 k" x0 WString to Byte[]& M, [$ b5 u& q2 t6 D* a8 L  R
public static byte[] StringToByteArray(String str) => str.AsByteArray();8 d3 q5 a; G% Q( D% g
BigInteger to Byte[]; `+ a3 u- |3 j6 g9 C7 f
public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();. x9 b' i" z0 H+ N* e( a
Byte to Byte[]
5 {0 {3 X5 I8 h9 c: O( V, n9 y  K你可能会认为下面这段代码看起来很好:
, j8 y( j* j# xpublic static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
& ], _$ x5 m& Q7 l/ J( l它可以通过编译,但在大多数情况下会返回意想不到的值。 这是因为不支持按照变量分配字节数组。 所以要避免使用这种转换。% ]& c* I* C, U! [+ s4 i
操作符和关键字
  W6 ?% f  A7 ~0 _- G, C正如官方文档中提到的,NeoVM支持大多数的c#操作符和关键字。补充说明如下:
0 a4 ^/ X9 A5 O, v2 x( VBool: AND, OR 和 NOT
- B( e3 L& ]0 I" v$ l" u; {支持操作符**“&&”,“||”** 和** “!”**
8 L4 X3 U; P" G5 Z$ hbool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// 分别代表false, false, true7 _; ]; n( X: y" O5 J
关键字: “ref” 和 “out”
+ B" U* T% X/ B关键字“ref”或“out”是C#语言的特性,用来允许将局部变量传给函数作为引用。Neo智能合约不支持这些关键字。( N( |6 d" ?+ `' s8 B/ `& t
关键字: “try-catch”, “throw”, “finally”2 N# R% Q) Q3 r. b; {% j
不支持这几个用于异常处理的关键字0 c4 B7 L) @0 E! o/ o
字节数组:级联和子数组# {( ]0 R7 @! k' `* w
`//Concatenation6 M, j/ R7 f8 d! f% Y
public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2);
5 W, t' Q4 q7 y  C% y  k//Get Byte array’s subarray
) W1 m5 a( l7 R( [public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);`% @  D6 z4 C: n# g
关键字 参数中的“This”/ C, J$ ?  q& L) ?
有时你需要定义类型的扩展,从而使逻辑更加简洁直观。 NeoVM支持关键字“This”。 以下示例代码显示了如何使用它。
2 d8 m1 s6 Q4 a+ H$ c0 Q5 g9 B// 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;    } }' r4 v. F, C+ i; U# {
使用上面的方法:- J8 x, b) b% {
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());
* t4 s% r, L5 d' `8 }字节数组:修改值
- {4 i3 g  G# j  V6 z% mNeoVM不支持可变字节操作。 所以我们需要拆分子数组,修改其中的一部分值,然后再将它们连接起来。 应将下面这个方法放入上面的ByteArrayExts类中。8 x# l0 `- @' [0 H
`public static class ByteArrayExts{
% J% O% s5 }8 O3 w$ [7 }8 ?//… previous functions …
' k; Y3 W* ]6 S1 ~public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){
8 S% j& K5 z+ z* Zbyte[] part1 = bytes.Sub(0,start);
5 K! f! r. u0 h9 Zint endIndex = newBytes.Length + start;
& B. `! _7 t7 L& H- Iif(endIndex - i+ M6 N# \) c
使用:  M8 F1 g2 R0 \; o
`byte[] orig = new byte[5]{1,2,3,4,5};7 w  ^& J6 K) D0 s) b4 Q' I
byte[] newValue = new byte[2]{6,7};
  Y6 M8 Q8 I  s- X//Replace the 3rd and 4th elements of orig byte array., i4 F/ b$ c1 V. x7 _7 U8 X
byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};`
/ i; t# G6 x% j3 J存储- \+ e, C# M" p% q
Storage / StorageMap类是与智能合约的链上持久化信息进行交互的唯一方式。 基本的CRUD操作是:
2 v9 T  W' M( z- a: y`//Create and Update: 1GAS/KB
5 {/ k6 Y! z& {; Q# a3 N) GStorage.Put(Storage.CurrentContext, key, value);
9 A+ K) |# {) T& I7 P//Read: 0.1GAS/time
2 M' x/ u- x0 C4 m5 XStorage.Get(Storage.CurrentContext, key);
  z$ k$ Q7 y( X! a# b//Delete: 0.1GAS/time5 x2 i, ?: h* |- p, W6 Z
Storage.Delete(Storage.CurrentContext, key);`# Y; b) D% y3 x5 A0 W5 D/ D8 o
在使用上面这几个方法时,有一些技巧:8 M3 B# V7 p, J" y
1.在调用Storage.Put()之前检查值是否保持不变。 如果不改变,这将节省0.9GAS。
$ F7 S! \! |; P. f3 X' @8 O0 z( D2.在调用Storage.Put()之前,检查新值是否为空。 如果为空,请改用Storage.Delete()。 这也将节省0.9GAS。
# M% H- D7 i7 z  K% v7 I`byte[] orig = Storage.Get(Storage.CurrentContext, key);9 L! h4 @" @+ m( }; }9 o& O
if (orig == value) return;//Don’t invoke Put if value is unchanged.
5 W; W" Z, B# t7 S8 gif (value.Length == 0){//Use Delete rather than Put if the new value is empty.) k3 k. d$ u: U" H& G7 V
Storage.Delete(Storage.CurrentContext, key);8 c6 K, y' B& f, J9 X: q
}
2 g) R* p( O1 i4 }. l; u# n! [else{. A2 @: W6 P: Y  ^6 I
Storage.Put(Storage.CurrentContext, key, value);; l0 ^* Y0 c& s) i6 S4 j
}`* ~  T* {" `! o% d# S
设计数据结构时预估长度接近但小于n KB。因为方法写2字节和写900字节的开销是一样的。如有必要,你甚至可以组合一些项。
& {# U2 |' ?" _
1 z3 p# N7 O" ?  j5 zBigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i
& a1 @. P( ^" k' a& }  q随机性
! q* R$ P5 g) T3 \" d# Z4 k# O生成随机值对于智能合约来说是一项挑战。& _  \- y, j( z  i( x4 t2 v0 i' m' t
首先,种子必须是区块链相关的确定性值。 否则,记账员就不能同意。 大多数Dapps会选择blockhash作为种子。但是使用这种方法的话,不同的用户在同一个区块中调用相同的SC方法会返回相同的结果。在Fabio Cardoso的文章中,引入了一种新的算法来同时使用blockhash和transactionID作为种子。
0 d4 b6 X5 K( Y: q" D# f* B' O( y/ G对于一些高度敏感的Dapps,专业用户可能会争辩说,记账员可以通过重新排序交易来干预blockhashes。 在这种情况下,generalkim00和maxpown3r提供了非对称熵的算法。 这个过程有点复杂,所以要想学习的话,可以点击这个链接阅读他们这个博彩的例子的智能合约源代码。7 z" ]! v& G, H7 f/ S7 R/ d
总结
. {& T$ Q6 S! u感谢阅读本教程。如果我们在开发智能合约时发现更多技巧的话,我会继续在这里更新它。感谢dprat0821在讨论中给予的帮助。感谢Fabio, generalkim00和maxpown3r的精彩想法。" b! z5 V2 k/ f5 v: y% Y
我的团队正在开发一款将人们内心深处的话语刻在NEO区块链上的游戏。谢谢你的意见和建议。
BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

一点评谱 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    1