Zastosowania inteligentnych kont Waves: od aukcji po programy bonusowe

Zastosowania inteligentnych kont Waves: od aukcji po programy bonusowe

Blockchain często kojarzy się wyłącznie z kryptowalutami, jednak obszary zastosowań technologii DLT są znacznie szersze. Jednym z najbardziej obiecujących obszarów wykorzystania blockchainu jest inteligentna umowa, która zawierana jest automatycznie i nie wymaga zaufania pomiędzy stronami, które ją zawarły.

RIDE – język inteligentnych kontraktów

Firma Waves opracowała specjalny język dla inteligentnych kontraktów – RIDE. Znajduje się w nim pełna dokumentacja tutaj. I tu - artykuł na ten temat na Habr.

Kontrakt RIDE jest predykatem i jako wynik zwraca wartość „prawda” lub „fałsz”. W związku z tym transakcja zostaje albo zarejestrowana w blockchainie, albo odrzucona. Inteligentny kontrakt w pełni gwarantuje spełnienie określonych warunków. Generowanie transakcji z kontraktu w RIDE nie jest aktualnie możliwe.

Obecnie istnieją dwa rodzaje inteligentnych kontraktów Waves: inteligentne konta i inteligentne aktywa. Konto inteligentne jest zwykłym kontem użytkownika, ale jest dla niego ustawiony skrypt, który kontroluje wszystkie transakcje. Skrypt konta inteligentnego może wyglądać na przykład tak:

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

tx to przetwarzana transakcja, którą dopuszczamy przy użyciu mechanizmu dopasowywania wzorców tylko wtedy, gdy nie jest to transakcja przelewu. Dopasowywanie wzorców w RIDE służy do sprawdzania rodzaju transakcji. Wszystkie istniejące konta można przetwarzać w skrypcie konta inteligentnego typy transakcji.

Skrypt może także deklarować zmienne, używać konstrukcji „if-then-else” i innych metod pełnego sprawdzania warunków. Aby mieć pewność, że kontrakty mają możliwą do udowodnienia kompletność i złożoność (koszt), którą można łatwo przewidzieć przed rozpoczęciem realizacji kontraktu, RIDE nie zawiera pętli ani instrukcji skoku.

Inne cechy kont Waves obejmują obecność „stanu”, czyli stanu konta. Do stanu konta możesz zapisać nieskończoną liczbę par (klucz, wartość) za pomocą transakcji danych (DataTransaction). Informacje te można następnie przetwarzać zarówno poprzez REST API, jak i bezpośrednio w inteligentnym kontrakcie.

Każda transakcja może zawierać szereg dowodów, w które można wpisać podpis uczestnika, identyfikator wymaganej transakcji itp.

Praca z RIDE przez IDE umożliwia podgląd skompilowanego widoku umowy (jeśli jest skompilowany), tworzenie nowych kont i ustawianie dla nich skryptów, a także wysyłanie transakcji poprzez linię poleceń.

Do pełnego cyklu, obejmującego utworzenie konta, zainstalowanie na nim inteligentnego kontraktu i wysłanie transakcji, możesz także skorzystać z biblioteki do interakcji z REST API (np. C#, C, Java, JavaScript, Python, Rust, Elixir) . Aby rozpocząć pracę z IDE wystarczy kliknąć przycisk NOWY.

Możliwości wykorzystania inteligentnych kontraktów są szerokie: od zakazu transakcji dla określonych adresów („czarna lista”) po złożone dApps.

Przyjrzyjmy się teraz konkretnym przykładom wykorzystania inteligentnych kontraktów w biznesie: przy prowadzeniu aukcji, ubezpieczeniach, tworzeniu programów lojalnościowych.

Aukcje

Jednym z warunków udanej aukcji jest przejrzystość: uczestnicy muszą mieć pewność, że nie da się manipulować ofertami. Można to osiągnąć dzięki blockchainowi, w którym niezmienne dane o wszystkich zakładach i czasie ich zawarcia będą dostępne dla wszystkich uczestników.

Na blockchainie Waves oferty można rejestrować w stanie konta aukcyjnego za pośrednictwem DataTransaction.

Możesz także ustawić czas rozpoczęcia i zakończenia aukcji za pomocą numerów bloków: częstotliwość generowania bloków w łańcuchu bloków Waves jest w przybliżeniu równa 60 sekundy.

1. Angielska aukcja ceny rosnącej

Uczestnicy aukcji w języku angielskim składają oferty, konkurując ze sobą. Każdy nowy zakład musi przewyższać poprzedni. Aukcja kończy się, gdy nie będzie już więcej oferentów, którzy przekroczą ostatnią ofertę. W takim przypadku oferent, który zaoferuje najwyższą cenę, musi podać podaną kwotę.

Istnieje również opcja aukcji, w której sprzedający ustala cenę minimalną za przedmiot, a cena ostateczna musi ją przekraczać. W przeciwnym razie przedmiot pozostaje niesprzedany.

W tym przykładzie pracujemy z kontem utworzonym specjalnie na potrzeby aukcji. Czas trwania aukcji wynosi 3000 bloków, a cena wywoławcza partii wynosi 0,001 FALI. Uczestnik może złożyć ofertę, wysyłając DataTransaction z kluczem „cena” i wartością swojej oferty.

Cena nowej oferty musi być wyższa niż aktualna cena za ten klucz, a uczestnik musi posiadać na swoim koncie co najmniej tokenów [new_bid + prowizja]. Adres licytanta musi być zapisany w polu „nadawca” w DataTransaction, a aktualna wysokość bloku ofertowego musi mieścić się w okresie aukcji.

Jeżeli na koniec aukcji uczestnik ustalił najwyższą cenę, może wysłać ExchangeTransaction, aby zapłacić za odpowiednią partię po określonej cenie i parze walutowej.

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. Holenderska aukcja cen malejących

Na aukcji w Holandii przedmiot jest początkowo oferowany po cenie wyższej niż cena, którą kupujący jest skłonny zapłacić. Cena maleje krok po kroku, aż jeden z uczestników zgodzi się na zakup przedmiotu po aktualnej cenie.

W tym przykładzie używamy tych samych stałych, co w poprzednim, a także kroku ceny, gdy delta maleje. Skrypt konta sprawdza, czy uczestnik rzeczywiście jako pierwszy postawił zakład. W przeciwnym razie DataTransaction nie zostanie zaakceptowana przez łańcuch bloków.

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. Aukcja „all pay”

„All-pay” to aukcja, w której wszyscy uczestnicy płacą stawkę, niezależnie od tego, kto wygra licytację. Każdy nowy uczestnik płaci ofertę, a uczestnik, który złoży ofertę maksymalną, wygrywa los.

W naszym przykładzie każdy uczestnik aukcji składa ofertę za pośrednictwem DataTransaction z (kluczem, wartością)* = („zwycięzca”, adres), („cena”, cena). Taka Transakcja Danych zostaje zatwierdzona tylko wtedy, gdy uczestnik ten posiada już Transakcję Transferu ze swoim podpisem i jego oferta jest wyższa od wszystkich poprzednich. Aukcja trwa do osiągnięcia 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)
}

Ubezpieczenia / crowdfunding

Rozważmy sytuację, w której trzeba ubezpieczyć majątek użytkowników od strat finansowych. Na przykład użytkownik chce mieć gwarancję, że w przypadku utraty wartości tokena będzie mógł odzyskać pełną kwotę zapłaconą za te tokeny i jest skłonny zapłacić rozsądną kwotę ubezpieczenia.

Aby to wdrożyć, należy wydać „tokeny ubezpieczeniowe”. Następnie na koncie ubezpieczającego instalowany jest skrypt, który pozwala na wykonanie tylko tych transakcji ExchangeTransactions, które spełniają określone warunki.

Aby zapobiec podwójnym wydatkom, należy wcześniej poprosić użytkownika o przesłanie transakcji DataTransaction na konto ubezpieczającego z wartością (klucz, wartość) = (purchaseTransactionId, SellOrderId) i zabronić wysyłania transakcji DataTransaction z kluczem, który został już wykorzystany.

Dlatego też dowody użytkownika muszą zawierać identyfikator transakcji zakupu tokena ubezpieczeniowego. Para walutowa musi być taka sama jak w transakcji zakupu. Koszt musi być również równy kosztowi ustalonemu w momencie zakupu, pomniejszony o cenę ubezpieczenia.

Przyjmuje się, że następnie konto ubezpieczeniowe nabywa od użytkownika tokeny ubezpieczeniowe po cenie nie niższej niż ta, po której je nabył: konto ubezpieczeniowe tworzy Transakcję Wymiany, użytkownik podpisuje zlecenie (o ile transakcja przebiegła prawidłowo), konto ubezpieczeniowe podpisuje drugie zamówienie i całą transakcję i wysyła je do blockchainu.

Jeżeli nie nastąpi zakup, użytkownik może utworzyć ExchangeTransaction zgodnie z zasadami opisanymi w skrypcie i wysłać transakcję do blockchainu. W ten sposób użytkownik może zwrócić pieniądze wydane na zakup ubezpieczonych tokenów.

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)
}

Token ubezpieczeniowy może zostać przekształcony w inteligentny zasób, na przykład uniemożliwiający jego przekazywanie osobom trzecim.

Schemat ten można wdrożyć także w przypadku tokenów crowdfundingowych, które w przypadku nie uzbierania wymaganej kwoty wracają do właścicieli.

Podatki transakcyjne

Inteligentne kontrakty znajdują zastosowanie również w przypadkach, gdy konieczne jest pobranie podatku od każdej transakcji kilkoma rodzajami aktywów. Można to zrobić za pomocą nowego zasobu z zainstalowanym sponsoring dla transakcji z inteligentnymi aktywami:

1. Wystawiamy FeeCoin, który będzie wysyłany do użytkowników po ustalonej cenie: 0,01 WAVES = 0,001 FeeCoin.

2. Ustaw sponsorowanie dla FeeCoin i kurs wymiany: 0,001 WAVES = 0,001 FeeCoin.

3. Ustaw następujący skrypt dla inteligentnego zasobu:

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 za każdym razem, gdy ktoś przekaże N inteligentnych aktywów, da ci FeeCoin w ilości N/taxDivisor (który można od ciebie kupić za 10 *N/taxDivisor WAVES), a ty dasz górnikowi N/taxDivisor WAVES. W efekcie Twój zysk (podatek) wyniesie 9*N/taxDivisor WAVES.

Możesz także dokonać opodatkowania za pomocą inteligentnego skryptu aktywów i 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 i programy lojalnościowe

Cashback to rodzaj programu lojalnościowego, w ramach którego kupujący odzyskuje część kwoty wydanej na produkt lub usługę.

Realizując tę ​​sprawę za pomocą inteligentnego konta, musimy sprawdzić dowody w taki sam sposób, jak to zrobiliśmy w przypadku ubezpieczenia. Aby zapobiec podwójnym wydatkom, użytkownik musi wysłać transakcję DataTransaction z (kluczem, wartością) = (purchaseTransactionId, cashbackTransactionId) przed otrzymaniem zwrotu pieniędzy.

Musimy także ustawić zakaz dla istniejących kluczy za pomocą DataTransaction. cashbackDivisor - jednostka podzielona przez udział w cashbacku. Te. jeśli udział w cashbacku wynosi 0.1, to 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)
}

Zamiana atomowa

Atomic swap umożliwia użytkownikom wymianę aktywów bez pomocy giełdy. W przypadku swapu atomowego obaj uczestnicy transakcji mają obowiązek potwierdzić ją w określonym terminie.

Jeżeli choć jeden z uczestników nie dostarczy prawidłowego potwierdzenia transakcji w terminie przewidzianym na transakcję, transakcja zostaje anulowana, a wymiana nie następuje.

W naszym przykładzie użyjemy następującego skryptu konta inteligentnego:

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
}

W następnym artykule przyjrzymy się wykorzystaniu inteligentnych kont w instrumentach finansowych, takich jak opcje, kontrakty futures i bony.

Źródło: www.habr.com

Dodaj komentarz