В этой статье мы разберем на двух примерах, как написать смарт контракт на языке 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 для взаимодействия с серверной блокчейна:
Пример 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 здесь.
Тестирование договора
Существует два способа проверить контракт:
- Использовать CLI
- Использовать Golang SDK
Заключение
В этой статье мы рассказали, как написать смарт-контракт на Ontolgy Wasm c использованием API блокчейна. Осталось решить проблему конфиденциальности, чтобы смарт-контракт превратился в полноценный продукт. На данном этапе кода, любой может получить хэш красного конверта, отслеживая записи о контракте, это означает, что претендовать на долю в конверте может кто угодно. Эта проблему можно решить просто — определим список учетных записей, которые могут претендовать на конверт при его создании. При желании, эту функцию тоже можно протестировать.
Получи грант Ontology на разработку dApp от $20,000
Подать заявку на программу талантов Ontology для студентов
Вы разработчик? Присоединяйтесь к нашему техническому сообществу на Discord. Кроме того, загляните в Центр разработчиков на нашем сайте, там вы можете найти инструменты разработчика, документацию и многое другое.