В этом уроке вы найдете несколько практических советов. Одна из самых больших проблем при разработке смарт-контрактов C # NEO связана с языковыми функциями, поддерживаемыми NeoVM, о которых на практике не говорит официальный документ. Также есть несколько полезных советов по взаимодействию с хранилищем и генерации случайности. Наслаждайтесь взломом.
Преобразование типов
Основным типом, поддерживаемым NeoVM, является массив байтов (Byte []), затем обычно используемые логические значения, String и BigInteger. Существуют и другие целочисленные типы, такие как Int8, Int64, UInt16, long, ulong и т. Д., Которые можно неявно преобразовать в BigInteger. Плавающий не поддерживается.
Итак, давайте сосредоточимся на преобразованиях между Byte [], Boolean, String и BigInteger. Примечание. Некоторые преобразования не определены официально, в данном случае я пытаюсь найти наиболее разумную реализацию.
Из байта [] в логическое
Хотя выглядит самым простым, на самом деле прямого преобразования нет. Официальная инструкция только упоминает, что False равно int 0. Итак, предположим, что True равно всем остальным значениям, в то время как пустой массив байтов также равен False. Итак, мы определяем следующую функцию:
public static bool BytesToBool(byte[] data) => data[0] != 0;
Тогда мы получим следующий результат:
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
Байт [] в строку
Это напрямую предоставлено Neo.SmartContract.Framework.Helper
public static string BytesToByte(byte[] data) => data.AsString();
Byte [] в BigInteger
public static BigInteger BytesToBigInteger(byte[] data) => data.AsBigInteger();
Логическое в байтовое []
Это также требует ручного преобразования.
public static byte[] Bool2Bytes(bool val) => val? (new byte[1]{1}): (new byte[1]{0});
Строка в байт []
public static byte[] StringToByteArray(String str) => str.AsByteArray();
BigInteger в Byte []
public static byte[] BigIntegerToByteArray(BigInteger bigInteger) => bigInteger.AsByteArray();
Байт в Байт []
Вы можете подумать, что следующий фрагмент хорош:
public static byte[] Byte2Bytes(byte b) => new byte[1] { b };//WRONG IMPLEMENTATION!!!
он проходит компиляцию, но в большинстве случаев возвращает неожиданное значение. Это связано с тем, что выделение байтового массива по переменным не поддерживается. Так что просто избегайте этого преобразования.
Операторы и ключевые слова
Как упоминалось в официальном документе, NeoVM поддерживает большинство операторов и ключевых слов C #. Вот дополнительная информация:
Bool: И, ИЛИ и НЕ
Операторы «&&», «||» и "!" поддерживаются.
bool b = true; bool a = false; Runtime.Notify(!b, b && a, b || a);// Message of false, false, true
Ключевое слово: «ref» и «out»
Ключевые слова «ref» или «out» - это возможности C #, позволяющие передавать локальные переменные в функцию в качестве ссылок. Эти ключевые слова не поддерживаются в смарт-контракте Neo.
Ключевое слово: «попытаться поймать», «бросить», «наконец»
Ключевые слова обработки исключений не поддерживаются.
Массив байтов: конкатенация и подмассив
//Concatenation public static byte[] JoinByteArrays(byte[] ba1, byte[] ba2) => ba1.Concat(ba2); //Get Byte array's subarray public static byte[] SubBytes(byte[] data, int start, int length) => Helper.Range(data, start, length);
Ключевое слово «Это» в параметре
Иногда вы хотите определить расширение типов, чтобы сделать логику более сжатой и интуитивно понятной. NeoVM поддерживает ключевое слово «Это». В следующем примере кода показано, как его использовать.
// 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; } }
Чтобы использовать вышеуказанные функции:
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());
Байтовый массив: измените значение
NeoVM не поддерживает побайтовые манипуляции с переменными. Итак, нам нужно разделить подмассивы, изменить некоторые части, а затем объединить их обратно. Следующая функция должна быть помещена в вышеуказанный класс ByteArrayExts.
public static class ByteArrayExts{ //... previous functions ... public static byte[] Modify(this byte[] bytes, int start, byte[] newBytes){ byte[] part1 = bytes.Sub(0,start); int endIndex = newBytes.Length + start; if(endIndex < bytes.Length){ byte[] part2 = bytes.Sub(endIndex, bytes.Length-endIndex); return part1.Concat(newBytes).Concat(part2); } else{ return part1.Concat(newBytes); } } }
Использование:
byte[] orig = new byte[5]{1,2,3,4,5}; byte[] newValue = new byte[2]{6,7}; //Replace the 3rd and 4th elements of orig byte array. byte[] ret = orig.Modify(2, newValue);//return {1,2,6,7,5};
Хранилище
Класс Storage / StorageMap - это единственные способы взаимодействия с постоянной информацией вашего смарт-контракта в цепочке. Основные операции CRUD:
//Create and Update: 1GAS/KB Storage.Put(Storage.CurrentContext, key, value); //Read: 0.1GAS/time Storage.Get(Storage.CurrentContext, key); //Delete: 0.1GAS/time Storage.Delete(Storage.CurrentContext, key);
Вот несколько советов по использованию вышеуказанных функций:
- Перед вызовом Storage.Put () проверьте, не изменилось ли значение. Это сэкономит 0,9 ГАЗА, если оно не изменится.
- Перед вызовом Storage.Put () проверьте, пусто ли новое значение. Если пусто, используйте вместо него Storage.Delete (). Это также сэкономит 0,9 ГАЗА.
byte[] orig = Storage.Get(Storage.CurrentContext, key); if (orig == value) return;//Don't invoke Put if value is unchanged. if (value.Length == 0){//Use Delete rather than Put if the new value is empty. Storage.Delete(Storage.CurrentContext, key); } else{ Storage.Put(Storage.CurrentContext, key, value); }
3. Разработайте структуру данных с приблизительной длиной, близкой к n КБ. Потому что функция записи 2Bytes стоит столько же, как и функция для 900Bytes. При необходимости можно даже комбинировать некоторые предметы.
BigInteger[] userIDs = //....Every ID takes constantly 32 Bytes. int i = 0; BigInteger batch = 0; while( i< userIDs.Length){ byte[] record = new byte[0]; for(int j = 0; j< 31;j++){//31x32 = 992 Bytes. int index = i + j; if( index == userIDs.Length ) return; else{ record=record.Concat(userIDs[index].AsByteArray()); } } //This cost only 1GAS rather than 31GAS. Storage.Put(Storage.CurrentContext, batch.AsByteArray(), record); batch = batch + 1; ++i; }
Случайность
Генерация случайных значений - сложная задача для смарт-контрактов.
Во-первых, начальное значение должно быть детерминированным значением, связанным с блокчейном. В противном случае бухгалтеры не могут согласиться с этим. Большинство Dapps выбрали бы хеш-блокировку в качестве начального значения. Но при таком подходе разные пользователи, вызывающие одну и ту же функцию SC во время одного и того же блока, будут возвращать один и тот же результат. В статье Фабио Кардосо представлен новый алгоритм, использующий как хеш-блокировку, так и идентификатор транзакции.
Для некоторых высокочувствительных Dapps профессиональные пользователи могут возразить, что бухгалтеры могут вмешаться в хеширование блоков путем переупорядочения транзакций. В этом случае алгоритм Асимментропии предоставляется generalkim00 и maxpown3r. Процесс немного сложен, поэтому, пожалуйста, просто прочитайте исходный код смарт-контракта на примере лотереи ЗДЕСЬ, чтобы узнать.
Резюме
Спасибо, что прочитали это руководство. Я продолжу обновлять его, когда мы дадим больше советов по разработке смарт-контрактов. Благодарим dprat0821 за помощь в обсуждениях. Благодарим Fabio, generalkim00 и maxpown3r за отличные идеи.
Моя команда работает над игрой, чтобы вырезать искренние слова людей на блокчейне NEO. Спасибо за любые комментарии и предложения.
Адрес для пожертвований NEO: AKJEavjHZ3v96kxh7nWKpt4nVCj7VtirCg