Aplikácie inteligentných účtov Waves: od aukcií po bonusové programy

Aplikácie inteligentných účtov Waves: od aukcií po bonusové programy

Blockchain sa často spája len s kryptomenami, no oblasti použitia technológie DLT sú oveľa širšie. Jednou z najsľubnejších oblastí využitia blockchainu je inteligentná zmluva, ktorá sa vykonáva automaticky a nevyžaduje dôveru medzi stranami, ktoré ju uzavreli.

RIDE – jazyk pre smart kontrakty

Waves vyvinul špeciálny jazyk pre inteligentné zmluvy – RIDE. Jeho kompletná dokumentácia sa nachádza tu. A tu - článok na túto tému na Habr.

Kontrakt RIDE je predikát a ako výstup vracia „pravda“ alebo „nepravda“. V súlade s tým je transakcia buď zaznamenaná v blockchaine alebo odmietnutá. Smart kontrakt plne garantuje splnenie stanovených podmienok. Generovanie transakcií zo zmluvy v RIDE momentálne nie je možné.

Dnes existujú dva typy inteligentných zmlúv Waves: inteligentné účty a inteligentné aktíva. Smart účet je bežný používateľský účet, no je preň nastavený skript, ktorý riadi všetky transakcie. Skript inteligentného účtu môže vyzerať napríklad takto:

match tx {
  case t: TransferTransaction | MassTransferTransaction => false
  case _ => true
}

tx je spracovávaná transakcia, ktorú povoľujeme pomocou mechanizmu porovnávania vzorov, iba ak nejde o transakciu prevodu. Zhoda vzorov v RIDE sa používa na kontrolu typu transakcie. Všetky existujúce účty je možné spracovať v skripte inteligentných účtov typy transakcií.

Skript môže tiež deklarovať premenné, používať konštrukty „if-then-else“ a ďalšie metódy na úplnú kontrolu podmienok. Aby sa zabezpečilo, že zmluvy budú mať preukázateľnú úplnosť a zložitosť (náklady), ktoré sa dajú ľahko predvídať pred začatím vykonávania zmluvy, RIDE neobsahuje slučky ani skokové príkazy.

Medzi ďalšie funkcie účtov Waves patrí prítomnosť „stavu“, teda stavu účtu. Pomocou dátových transakcií (DataTransaction) môžete do stavu účtu zapisovať nekonečné množstvo párov (kľúč, hodnota). Tieto informácie je potom možné spracovať ako cez REST API, tak aj priamo v smart kontrakte.

Každá transakcia môže obsahovať rad dokladov, do ktorých je možné zadať podpis účastníka, ID požadovanej transakcie a pod.

Práca s RIDE cez IDE umožňuje vidieť zostavený pohľad na zmluvu (ak je zostavená), vytvárať nové účty a nastavovať pre ňu skripty, ako aj odosielať transakcie cez príkazový riadok.

Pre celý cyklus, vrátane vytvorenia účtu, inštalácie inteligentného kontraktu naň a odosielania transakcií, môžete tiež použiť knižnicu na interakciu s REST API (napríklad C#, C, Java, JavaScript, Python, Rust, Elixir) . Ak chcete začať pracovať s IDE, stačí kliknúť na tlačidlo NOVÝ.

Možnosti využitia smart kontraktov sú široké: od zákazu transakcií na určité adresy („čierna listina“) až po zložité dApps.

Teraz sa pozrime na konkrétne príklady využitia inteligentných zmlúv v podnikaní: pri realizácii aukcií, poistení a vytváraní vernostných programov.

Aukcie

Jednou z podmienok úspešnej aukcie je transparentnosť: účastníci si musia byť istí, že nie je možné manipulovať s ponukami. To sa dá dosiahnuť vďaka blockchainu, kde budú všetkým účastníkom dostupné nemenné údaje o všetkých stávkach a čase, kedy boli uzavreté.

Na blockchaine Waves môžu byť ponuky zaznamenávané v stave aukčného účtu prostredníctvom DataTransaction.

Čas začiatku a konca aukcie môžete nastaviť aj pomocou čísel blokov: frekvencia generovania blokov v blockchaine Waves sa približne rovná 60 sekúnd.

1. Anglická aukcia vzostupných cien

Účastníci anglickej aukcie si navzájom konkurujú. Každá nová stávka musí prevýšiť predchádzajúcu. Aukcia končí, keď už nie sú žiadni záujemcovia, ktorí by prekročili poslednú ponuku. V takom prípade musí uvedenú sumu poskytnúť uchádzač s najvyššou ponukou.

Existuje aj možnosť aukcie, pri ktorej predávajúci stanoví minimálnu cenu za žreb a konečná cena ju musí prekročiť. V opačnom prípade zostane zásielka nepredaná.

V tomto príklade pracujeme s účtom špeciálne vytvoreným pre aukciu. Trvanie aukcie je 3000 blokov a vyvolávacia cena lotu je 0,001 VLNY. Účastník môže zadať ponuku odoslaním DataTransaction s kľúčom „cena“ a hodnotou svojej ponuky.

Cena novej ponuky musí byť vyššia ako aktuálna cena tohto kľúča a účastník musí mať na účte aspoň [nová_ponuka + provízia] tokenov. Adresa dražiteľa musí byť zaznamenaná v poli „odosielateľ“ v DataTransaction a aktuálna výška bloku ponuky musí byť v rámci aukčného obdobia.

Ak na konci aukcie účastník nastavil najvyššiu cenu, môže poslať ExchangeTransaction, aby zaplatil za zodpovedajúci lot za stanovenú cenu a menový pár.

let startHeight = 384120
let finishHeight = startHeight + 3000
let startPrice = 100000
 
#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)
let token = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
 
match tx {
case d : DataTransaction =>
  #проверяем, задана ли в стейте цена
  let currentPrice = if isDefined(getInteger(this, "price"))
 
                      #извлекаем цену из стейта
                      then extract(getInteger(this, "price"))
                      else startPrice
 
  #извлекаем цену из транзакции
  let newPrice = extract(getInteger(d.data, "price"))
  let priceIsBigger = newPrice > currentPrice
  let fee = 700000
  let hasMoney = wavesBalance(tx.sender) + fee >= newPrice
 
  #убеждаемся, что в текущей транзакции два поля и что отправитель совпадает с указанным в транзакции
  let correctFields = size(d.data) == 2 &&      
      d.sender == addressFromString(extract(getString(d.data,"sender")))
  startHeight <= height && height <= finishHeight && priceIsBigger && hasMoney && correctFields
case e : ExchangeTransaction =>
  let senderIsWinner = e.sender == addressFromString(extract(getString(this, "sender"))) #убеждаемся, что лот обменивает тот, кто его выиграл
  let correctAssetPair = e.sellOrder.assetPair.amountAsset == token && ! isDefined(e.sellOrder.assetPair.priceAsset)
  let correctAmount = e.amount == 1
  let correctPrice = e.price == extract(getInteger(this, "price"))
 
  height > finishHeight && senderIsWinner && correctAssetPair && correctAmount && correctPrice
case _ => false
}

2. Holandská aukcia klesajúcich cien

V holandskej aukcii sa na začiatku ponúka veľa za cenu vyššiu, ako je kupujúci ochotný zaplatiť. Cena sa postupne znižuje, kým jeden z účastníkov nesúhlasí s kúpou pozemku za aktuálnu cenu.

V tomto príklade používame rovnaké konštanty ako v predchádzajúcom, ako aj cenový krok, keď delta klesá. Skript účtu skontroluje, či je účastník skutočne prvým, kto vsadil. V opačnom prípade nebude DataTransaction blockchainom akceptovaná.

let startHeight = 384120
let finishHeight = startHeight + 3000
let startPrice = 100000000
let delta = 100
 
#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)
let token = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
match tx {
case d : DataTransaction =>
  let currentPrice = startPrice - delta * (height - startHeight)
 
  #извлекаем из поступившей дата-транзакции поле "price"
  let newPrice = extract(getInteger(d.data, "price"))
 
  #убеждаемся, что в стейте текущего аккаунта не содержится поля "sender"
  let noBetsBefore = !isDefined(getInteger(this, "sender"))
  let fee = 700000
  let hasMoney = wavesBalance(tx.sender) + fee >= newPrice
 
  #убеждаемся, что в текущей транзакции только два поля
  let correctFields = size(d.data) == 2 && newPrice == currentPrice && d.sender == addressFromString(extract(getString(d.data, "sender")))
  startHeight <= height && height <= finishHeight && noBetsBefore && hasMoney && correctFields
case e : ExchangeTransaction =>
 
  #убеждаемся, что отправитель текущей транзакции указан в стейте аккаунта по ключу sender
  let senderIsWinner = e.sender == addressFromString(extract(getString(this, "sender")))
 
  #убеждаемся, что аmount ассета указан корректно, и что прайс-ассет - waves
  let correctAssetPair = e.sellOrder.assetPair.amountAsset == token && ! isDefined(e.sellOrder.assetPair.priceAsset)
  let correctAmount = e.amount == 1
  let correctPrice = e.price == extract(getInteger(this, "price"))
  height > finishHeight && senderIsWinner && correctAssetPair && correctAmount && correctPrice
case _ => false
}

3. Aukcia „všetko platí“

„All-pay“ je aukcia, v ktorej všetci účastníci platia ponuku bez ohľadu na to, kto vyhrá žreb. Každý nový účastník zaplatí ponuku a účastník, ktorý urobí maximálnu ponuku, vyhráva.

V našom príklade každý účastník aukcie zadáva ponuku prostredníctvom DataTransaction s (kľúč, hodnota)* = („víťaz“, adresa), („cena“, cena). Takáto DataTransakcia je schválená iba v prípade, že tento účastník už má Transakciu s prevodom s podpisom a jeho ponuka je vyššia ako všetky predchádzajúce. Aukcia pokračuje až do dosiahnutia endHeight.

let startHeight = 1000
let endHeight = 2000
let this = extract(tx.sender)
let token = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
match tx {
 case d: DataTransaction =>
   #извлекаем из поступившей дата-транзакции поле "price"
   let newPrice = extract(getInteger(d.data, "price"))
 
   #извлекаем из пруфов транзакции публичный ключ аккаунта
   let pk = d.proofs[1]
   let address = addressFromPublicKey(pk)
 
   #извлекаем транзакцию доказательство из пруфов поступившей дата транзакции
   let proofTx = extract(transactionById(d.proofs[2]))
   
   height > startHeight && height < endHeight
   && size(d.data) == 2
   #убеждаемся, что адрес победителя, извлеченный из текущей транзакции, совпадает с адресом, извлеченным из пруфов
   && extract(getString(d.data, "winner")) == toBase58String(address.bytes)
   && newPrice > extract(getInteger(this, "price"))
   #проверяем, что транзакция подписана
   && sigVerify(d.bodyBytes, d.proofs[0], d.proofs[1])
   #проверяем корректность транзакции, указанной в пруфах
   && match proofTx {
     case tr : TransferTransaction =>
       tr.sender == address &&
       tr.amount == newPrice
     case _ => false
   }
 case t: TransferTransaction =>
 sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
 || (
   height > endHeight
   && extract(getString(this, "winner")) == toBase58String((addressFromRecipient(t.recipient)).bytes)
   && t.assetId == token
   && t.amount == 1
 )
 case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}

Poistenie / Crowdfunding

Zoberme si situáciu, keď potrebujete poistiť majetok používateľov proti finančným stratám. Používateľ chce napríklad záruku, že ak sa token znehodnotí, bude môcť získať späť plnú sumu zaplatenú za tieto tokeny a je ochotný zaplatiť primeranú sumu poistenia.

Na implementáciu je potrebné vydať „poistné tokeny“. Potom sa na účet poistenca nainštaluje skript, ktorý umožní vykonať iba tie ExchangeTransactions, ktoré spĺňajú určité podmienky.

Aby ste predišli dvojitým výdavkom, musíte vopred požiadať používateľa o odoslanie DataTransaction na účet poistenca s (kľúč, hodnota) = (purchaseTransactionId, sellOrderId) a zakázať odosielanie DataTransaction s kľúčom, ktorý už bol použitý.

Dôkazy používateľa preto musia obsahovať ID transakcie nákupu poistného tokenu. Menový pár musí byť rovnaký ako pri nákupnej transakcii. Náklady sa tiež musia rovnať nákladom stanoveným v čase nákupu mínus cena poistenia.

Rozumie sa, že následne poistný účet nakúpi od užívateľa poistné tokeny za cenu, ktorá nie je nižšia ako cena, za ktorú ich kúpil: poistný účet vytvorí ExchangeTransaction, užívateľ podpíše objednávku (ak je transakcia dokončená správne), poistný účet podpíše druhú objednávku a celú transakciu a odošle ju do blockchainu.

Ak nedôjde k nákupu, používateľ môže vytvoriť ExchangeTransaction podľa pravidiel opísaných v skripte a odoslať transakciu do blockchainu. Užívateľ tak môže vrátiť peniaze vynaložené na nákup poistených tokenov.

let insuranceToken = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
 
#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)
let freezePeriod = 150000
let insurancePrice = 10000
match tx {
 
 #убеждаемся, что, если поступила дата-транзакция, то у нее ровно одно поле и в стейте еще нет такого ключа
 case d : DataTransaction => size(d.data) == 1 && !isDefined(getBinary(this, d.data[0].key))
 case e : ExchangeTransaction =>
 
   #если у транзакции нет седьмого пруфа, проверяем корректность подписи
   if !isDefined(e.proofs[7]) then
     sigVerify(e.bodyBytes, e.proofs[0], e.senderPublicKey)
   else
     #если у транзакции есть седьмой пруф, извлекаем из него транзакцию и узнаём её высоту
     let purchaseTx = transactionById(e.proofs[7])
     let purchaseTxHeight = extract(transactionHeightById(e.proofs[7]))
    
     #обрабатываем транзакцию из пруфа
     match purchaseTx {
       case purchase : ExchangeTransaction =>
         let correctSender = purchase.sender == e.sellOrder.sender
         let correctAssetPair = e.sellOrder.assetPair.amountAsset == insuranceToken &&
                                purchase.sellOrder.assetPair.amountAsset == insuranceToken &&
                                e.sellOrder.assetPair.priceAsset == purchase.sellOrder.assetPair.priceAsset
         let correctPrice = e.price == purchase.price - insurancePrice && e.amount == purchase.amount
         let correctHeight = height > purchaseTxHeight + freezePeriod
 
         #убеждаемся, что в транзакции-пруфе указан верный ID текущей транзакции
         let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == e.sellOrder.id
         correctSender && correctAssetPair && correctPrice && correctHeight && correctProof
     case _ => false
   }
 case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}

Poistný token sa môže stať inteligentným aktívom, napríklad na zákaz jeho prevodu na tretie strany.

Túto schému je možné zaviesť aj pre crowdfundingové tokeny, ktoré sa v prípade nevyzbierania požadovanej sumy vracajú majiteľom.

Dane z transakcií

Smart kontrakty sú použiteľné aj v prípadoch, keď je potrebné vyberať daň z každej transakcie s viacerými druhmi aktív. To možno vykonať prostredníctvom nového majetku s nainštalovaným sponzorstvo pre transakcie s inteligentnými aktívami:

1. Vydávame FeeCoin, ktorý bude používateľom zasielaný za pevnú cenu: 0,01 VLNY = 0,001 FeeCoin.

2. Nastavte sponzorstvo pre FeeCoin a výmenný kurz: 0,001 VLNY = 0,001 FeeCoin.

3. Nastavte nasledujúci skript pre inteligentné aktívum:

let feeAssetId = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
let taxDivisor = 10
 
match tx {
  case t: TransferTransaction =>
    t.feeAssetId == feeAssetId && t.fee == t.amount / taxDivisor
  case e: ExchangeTransaction | MassTransferTransaction => false
  case _ => true
}

Teraz vždy, keď niekto prevedie N inteligentných aktív, dá vám FeeCoin vo výške N/taxDivisor (ktorý je možné u vás zakúpiť za 10 *N/daňového deliteľa VLNY) a vy dáte baníkovi N/taxDivisor VLNY. V dôsledku toho bude váš zisk (daň) 9*N / daňDivisor VLNY.

Zdanenie môžete vykonať aj pomocou inteligentného skriptu aktív a MassTransferTransaction:

let taxDivisor = 10
 
match tx {
  case t : MassTransferTransaction =>
    let twoTransfers = size(t.transfers) == 2
    let issuerIsRecipient = t.transfers[0].recipient == addressFromString("3MgkTXzD72BTfYpd9UW42wdqTVg8HqnXEfc")
    let taxesPaid = t.transfers[0].amount >= t.transfers[1].amount / taxDivisor
    twoTransfers && issuerIsRecipient && taxesPaid
  case _ => false
}

Cashback a vernostné programy

Cashback je typ vernostného programu, v ktorom kupujúci dostane späť časť sumy vynaloženej na produkt alebo službu.

Pri realizácii tohto prípadu pomocou smart účtu musíme skontrolovať dôkazy rovnakým spôsobom ako v prípade poistenia. Aby sa predišlo dvojitým výdavkom, používateľ musí pred prijatím cashbacku odoslať DataTransaction s (kľúč, hodnota) = (purchaseTransactionId, cashbackTransactionId).

Musíme tiež nastaviť zákaz existujúcich kľúčov pomocou DataTransaction. cashbackDivisor - jednotka delená podielom cashbacku. Tie. ak je podiel cashbacku 0.1, potom cashbackDivisor 1 / 0.1 = 10.

let cashbackToken = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
 
#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)
let cashbackDivisor = 10
match tx {
 
 #убеждаемся, что, если поступила дата-транзакция, то у нее ровно одно поле и в стейте еще нет такого ключа
 case d : DataTransaction => size(d.data) == 1 && !isDefined(getBinary(this, d.data[0].key))
 case e : TransferTransaction =>
 
   #если у транзакции нет седьмого пруфа, проверяем корректность подписи
   if !isDefined(e.proofs[7]) then
     sigVerify(e.bodyBytes, e.proofs[0], e.senderPublicKey)
   else
 
     #если у транзакции есть седьмой пруф, извлекаем из него транзакцию и узнаём её высоту
     let purchaseTx = transactionById(e.proofs[7])
     let purchaseTxHeight = extract(transactionHeightById(e.proofs[7]))
    
     #обрабатываем транзакцию из пруфа
     match purchaseTx {
       case purchase : TransferTransaction =>
         let correctSender = purchase.sender == e.sender
         let correctAsset = e.assetId == cashbackToken
         let correctPrice = e.amount == purchase.amount / cashbackDivisor
 
         #убеждаемся, что в транзакции-пруфе указан верный ID текущей транзакции
         let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == e.id
         correctSender && correctAsset && correctPrice && correctProof
     case _ => false
   }
 case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}

Výmena atómov

Atomic swap umožňuje používateľom vymieňať si aktíva bez pomoci výmeny. Pri atomickom swape sú obaja účastníci transakcie povinní potvrdiť ju v určitom časovom období.

Ak aspoň jeden z účastníkov neposkytne správne potvrdenie transakcie v čase určenom na transakciu, transakcia sa zruší a výmena sa neuskutoční.

V našom príklade použijeme nasledujúci skript inteligentného účtu:

let Bob = Address(base58'3NBVqYXrapgJP9atQccdBPAgJPwHDKkh6A8')
let Alice = Address(base58'3PNX6XwMeEXaaP1rf5MCk8weYeF7z2vJZBg')
 
let beforeHeight = 100000
 
let secret = base58'BN6RTYGWcwektQfSFzH8raYo9awaLgQ7pLyWLQY4S4F5'
match tx {
  case t: TransferTransaction =>
    let txToBob = t.recipient == Bob && sha256(t.proofs[0]) == secret && 20 + beforeHeight >= height
    let backToAliceAfterHeight = height >= 21 + beforeHeight && t.recipient == Alice
    txToBob || backToAliceAfterHeight
  case _ => false
}

V nasledujúcom článku sa pozrieme na využitie inteligentných účtov vo finančných nástrojoch, ako sú opcie, futures a zmenky.

Zdroj: hab.com

Pridať komentár