image

В этой статье мы разберем на двух примерах, как написать смарт контракт на языке C++, используя WASM на основе блокчейн сети Ontology. Сегодня, после нескольких месяцев стабильной работы в тестовом режиме, Ontology запустила WASM в основной сети, что позволяет безболезненно и с меньшими издержками переносить контракты dApp со сложной бизнес-логикой на блокчейн, тем самым значительно обогащая dApp экосистему.

Ontology Wasm также поддерживает создание смарт контрактов на языке Rust, об этом можно почитать тут.

Ниже рассмотрим два примера смарт-контракта: сначала напишем “Hello world!” и потом создадим виртуальные денежный конверт, который можно будет отправить другу в качестве подарка.

Разработка WASM-контракта с помощью С++



Пример 1. Hello World


Начнем с Hello World:

#include<ontiolib/ontio.hpp>
#include<stdio.h>

using namespace ontio;
class hello:public contract {
   public:
   using contract::contract:
   void sayHello(){
       printf("hello world!");
   }
};
ONTIO_DISPATCH(hello, (sayHello)); 

Создание контракта


Компилятор Ontology Wasm CDT содержит в себе точку входа и параметры парсинга, так что разработчикам не нужно переопределять методы ввода. Далее для написания логики сервиса можно вызывать API методы смарт-контракта.

ONTIO_DISPATCH(hello, (sayHello));

В приведенном примере мы пока поддерживаем только sayHello:

printf("hello world!");

«Hello World!” будет выведен в отладочный лог ноды. Во время непосредственного написания смарт контракта, printf может использоваться только для отладки, так как в самом смарт-контракте содержится более функциональные команды.

API смарт-контракта


Ontology Wasm предоставляет следующие API для взаимодействия с серверной блокчейна:

image

Пример 2: Денежный конверт


Теперь давайте рассмотрим более сложный пример с использованием API для разработки смарт-контракта Wasm.

В этом примере мы напишем виртуальные денежный конверт, аналог красного конверта (hongbao) — популярная функция китайского мессенджера Wechat, которая позволяет отправлять деньги друзьям в чате. Пользователь получает сообщение в виде красного конверта, открывает его и деньги автоматически зачисляются на баланс аккаунта.

В рамках смарт контракта, пользователи могут использовать этот контракт для отправки токенов ONT, ONG или OEP-4 с помощью виртуальных денежных конвертов своим друзьям, которые затем могут перевести токены на свои блокчейн кошельки.

Подготовка к созданию контракта


Сначала создадим исходный файл контракта и назовем его redEnvelope.cpp. Дальше нам понадобится три API для этого контракта:

  • createRedEnvelope: Создать денежный конверт
  • queryEnvelope: Запросить информацию о конверте
  • claimEnvelope: раскрыть конверт и получить деньги

#include<ontiolib/ontio.hpp>

using namespace ontio;

class redEnvlope: public contract{

}
ONTIO_DISPATCH(redEnvlope, (createRedEnvlope)(queryEnvlope)(claimEnvelope));

Теперь нам нужно сохранить key-value. В смарт-контракте данные сохраняются в контексте контракта в виде key-value, и нам нужно добавить префикс к KEY данными для последующего запроса.

Ниже определим три префикса, которые мы будем использовать:

std::string rePrefix = "RE_PREFIX_";
std::string sentPrefix = "SENT_COUNT_";
std::string claimPrefix = "CLAIM_PREFIX_";

Поскольку контракт поддерживает оба токена Ontology — ONT и ONG, можем заранее определить их адрес контракта. В отличие от стандартного смарт-контракта, адрес собственного контракта Ontology фиксирован и не является производным от хэш кода контракта.

address ONTAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
address ONGAddress = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2};

Далее необходимо сохранить в контракте информацию об используемом токене: адрес токена контракта, общая сумма конверта и количество конвертов.

struct receiveRecord{
       address account;   //User address
       asset amount;      //Received amount 
       ONTLIB_SERIALIZE(receiveRecord,(account)(amount))
   };

   struct envlopeStruct{
       address tokenAddress;   //Token asset address
       asset totalAmount;      //Total amount
       asset totalPackageCount; //Total number of red envelope
       asset remainAmount;      //Remaining amount
       asset remainPackageCount; //Remaining number of red envelope
       std::vector<struct receiveRecord> records;  //Received records
       ONTLIB_SERIALIZE( envlopeStruct,  (tokenAddress)(totalAmount)(totalPackageCount)(remainAmount)(remainPackageCount)(records) )
   };

Ниже приведена макрооперация, определенная Ontology Wasm CDT, которая используется для сериализации перед структуризацией данных.

ONTLIB_SERIALIZE(receiveRecord,(account)(amount))

Создание Конверта


Теперь, когда мы завершили необходимую подготовку, начнем разработку логики API.

1. При создании денежного конверта необходимо указать адрес владельца, количество и сумму конвертов, а также адрес токена:

bool createRedEnvlope(address owner,asset packcount, asset amount,address tokenAddr ){

       return true;
   }

2. Проверим наличие подписи владельца, в противном случае произведем rollback (откат транзакции) и выход:

ontio_assert(check_witness(owner),"checkwitness failed");

Примечание: ontio_assert(expr, errormsg): ложный expr возвращает ошибку и выход из контракта.

3. Если в конверте используется токен ONT, важно помнить, что,ONT не дробится (минимум 1 ONT). Тогда общая сумма денежного конверта должна быть больше или равна кол-ву токенов, чтобы обеспечить наличие по крайней мере 1 ONT в каждом конверте:

if (isONTToken(tokenAddr)){
           ontio_assert(amount >= packcount,"ont amount should greater than         packcount");
       }

4. Далее определим для владельца конверта общее количество денежных конвертов, которое он отправляет:

key sentkey = make_key(sentPrefix,owner.tohexstring());
asset sentcount = 0;
storage_get(sentkey,sentcount);
sentcount += 1;
storage_put(sentkey,sentcount);

5. Генерируем хэш конверта — идентификатор, который помечает этот конверт:

H256 hash ;

hash256(make_key(owner,sentcount),hash) ;

key rekey = make_key(rePrefix,hash256ToHexstring(hash));

6. Переведем токены в контракт. Узнаем адрес контракта, который сейчас выполняется, с помощью команды self_address (), затем мы передадим назначенную сумму токенов в контракт на основе типа токенов:

address selfaddr = self_address();
if (isONTToken(tokenAddr)){

bool result = ont::transfer(owner,selfaddr ,amount);
ontio_assert(result,"transfer native token failed!");

}else if (isONGToken(tokenAddr)){

bool result = ong::transfer(owner,selfaddr ,amount);
ontio_assert(result,"transfer native token failed!");
}else{
std::vector<char> params = pack(std::string("transfer"),owner,selfaddr,amount);
bool res;
call_contract(tokenAddr,params, res );

ontio_assert(res,"transfer oep4 token failed!");
}

Примечание 1: для ONT и ONG, Ontology Wasm CDT предоставляет API ont:: transfer для передачи токенов; токены OEP-4 необходимо отправлять с помощью обычного метода вызова кросс-контракта.

Примечание 2: подобно обычному адресу кошелька, адрес контракта может принимать любой тип токенов. Однако адрес контракта генерируется скомпилированным бинарным хэшем и, таким образом, не имеет соответствующего приватного ключа и не может использовать токены контракта. Если вы не установили приватный ключ, то вы не сможете управлять этими токенами.

7. Сохраним информацию о контракте в хранилище данных:

struct envlopeStruct es ;
es.tokenAddress = tokenAddr;
es.totalAmount = amount;
es.totalPackageCount = packcount;
es.remainAmount = amount;
es.remainPackageCount = packcount;
es.records = {};
storage_put(rekey, es);

8. Отправим оповещение о создании конверта. Это асинхронный процесс для вызова смарт-контракта, контракт также отправит уведомление о результате выполнения. Формат выполнения может быть определен автором контракта.

char buffer [100];
sprintf(buffer, "{\"states\":[\"%s\", \"%s\", \"%s\"]}","createEnvlope",owner.tohexstring().c_str(),hash256ToHexstring(hash).c_str());
notify(buffer);
return true;

Ура, денежный конверт почти готов. Теперь давайте посмотрим, как запросить информацию о конверте.

Запрос Конверта (Query


Логика запроса довольно простая, вам нужно только получить информацию и формат из хранилища данных, а затем произвести вывод:

std::string queryEnvlope(std::string hash){
key rekey = make_key(rePrefix,hash);
struct envlopeStruct es;
storage_get(rekey,es);
return formatEnvlope(es);
}

Примечание: для read-only операций смарт-контракта (например, query) можно проверить результат через pre-exec. В отличие от обычного вызова контракта, pre-exec не требует подписи кошелька и, следовательно, не требует комиссии в ONG. Сделав это, другие пользователи теперь могут претендовать на конверт, если у них есть хэш конверта (ID конверта).

Получение Конверта


На этом этапе мы уже успешно перевели токены в смарт-контракт, теперь, чтобы ваши друзья смогли успешно претендовать на долю в конверте, необходимо выслать им идентификатор конверта (хэш).

1. Для получения конверта, необходимо ввести адрес своего аккаунта и хэш конверта:

bool claimEnvlope(address account, std::string hash){
return true;
}

2. Далее контракт проверит подпись вашей учетной записи, чтобы убедиться, что вы являетесь ее владельцем. Каждая учетная запись может претендовать на конверт только один раз:

ontio_assert(check_witness(account),"checkwitness failed");
key claimkey = make_key(claimPrefix,hash,account);
asset claimed = 0 ;
storage_get(claimkey,claimed);
ontio_assert(claimed == 0,"you have claimed this envlope!");

3. Проверим, получен ли конверт в соответствии с хэш информацией, полученной из хранилища:

key rekey = make_key(rePrefix,hash);
struct envlopeStruct es;
storage_get(rekey,es);
ontio_assert(es.remainAmount > 0, "the envlope has been claimed over!");
ontio_assert(es.remainPackageCount > 0, "the envlope has been claimed over!");

4. Создание записи claim:

struct receiveRecord record ;
record.account = account;
asset claimAmount = 0;

5. Расчет суммы для каждого заявителя конверта.
Для последнего участника определяется сумма остатка, для любых других заявленная сумма определяется рандомным числом, вычисленным по хэшу текущего блока и текущей информации о конверте:

if (es.remainPackageCount == 1){
claimAmount = es.remainAmount;
record.amount = claimAmount;
}else{
H256 random = current_blockhash() ;
char part[8];
memcpy(part,&random,8);
uint64_t random_num = *(uint64_t*)part;
uint32_t percent = random_num % 100 + 1;
claimAmount = es.remainAmount * percent / 100;
//ont case
if (claimAmount == 0){
claimAmount = 1;
}else if(isONTToken(es.tokenAddress)){
if ( (es.remainAmount - claimAmount) < (es.remainPackageCount - 1)){
claimAmount = es.remainAmount - es.remainPackageCount + 1;
}
}
record.amount = claimAmount;
}
es.remainAmount -= claimAmount;
es.remainPackageCount -= 1;
es.records.push_back(record);

6. Зачисление средств


Соответствующая сумма токенов перечисляется на счет заявителей конверта в соответствии с результатом расчета:

address selfaddr = self_address();
if (isONTToken(es.tokenAddress)){
bool result = ont::transfer(selfaddr,account ,claimAmount);
ontio_assert(result,"transfer ont token failed!");
} else  if (isONGToken(es.tokenAddress)){
bool result = ong::transfer(selfaddr,account ,claimAmount);
ontio_assert(result,"transfer ong token failed!");
} else{
std::vector<char> params = pack(std::string("transfer"),selfaddr,account,claimAmount);
bool res = false;
call_contract(es.tokenAddress,params, res );
ontio_assert(res,"transfer oep4 token failed!");
}

7. Запишем информацию о получении средств и обновленную информацию о конверте в хранилище и отправим уведомление о выполнении контракта:

storage_put(claimkey,claimAmount);
       storage_put(rekey,es);
       char buffer [100];       
       std::sprintf(buffer, "{\"states\":[\"%s\",\"%s\",\"%s\",\"%lld\"]}","claimEnvlope",hash.c_str(),account.tohexstring().c_str(),claimAmount);

       notify(buffer);
       return true;

Как уже упоминалось выше, этот контракт может отправлять токены из контракта через claimEnvelope API. Что обеспечивает безопасность токенов, пока они находятся в конверте, поскольку никто не может вывести активы без выполнения необходимых требований.

Готово! Вы написали ваш первый смарт-контракт. Полный код контракта можно найти на GitHub здесь.

Тестирование договора


Существует два способа проверить контракт:

  1. Использовать CLI
  2. Использовать Golang SDK

Заключение


В этой статье мы рассказали, как написать смарт-контракт на Ontolgy Wasm c использованием API блокчейна. Осталось решить проблему конфиденциальности, чтобы смарт-контракт превратился в полноценный продукт. На данном этапе кода, любой может получить хэш красного конверта, отслеживая записи о контракте, это означает, что претендовать на долю в конверте может кто угодно. Эта проблему можно решить просто — определим список учетных записей, которые могут претендовать на конверт при его создании. При желании, эту функцию тоже можно протестировать.



Получи грант Ontology на разработку dApp от $20,000

Подать заявку на программу талантов Ontology для студентов



Вы разработчик? Присоединяйтесь к нашему техническому сообществу на Discord. Кроме того, загляните в Центр разработчиков на нашем сайте, там вы можете найти инструменты разработчика, документацию и многое другое.

Ontology


Комментарии (0)