Aplicații ale conturilor inteligente Waves: de la licitații la programe bonus

Aplicații ale conturilor inteligente Waves: de la licitații la programe bonus

Blockchain-ul este adesea asociat doar cu criptomonede, dar domeniile de aplicare a tehnologiei DLT sunt mult mai largi. Una dintre cele mai promițătoare domenii de utilizare a blockchain-ului este un contract inteligent care se execută automat și nu necesită încredere între părțile care l-au încheiat.

RIDE – un limbaj pentru contractele inteligente

Waves a dezvoltat un limbaj special pentru contractele inteligente – RIDE. Se găsește documentația sa completă aici. Si aici - articol pe acest subiect pe Habr.

Contractul RIDE este un predicat și returnează „adevărat” sau „fals” ca ieșire. În consecință, tranzacția este fie înregistrată în blockchain, fie respinsă. Contractul inteligent garantează pe deplin îndeplinirea condițiilor specificate. Generarea tranzacțiilor dintr-un contract în RIDE nu este în prezent posibilă.

Astăzi există două tipuri de contracte inteligente Waves: conturi inteligente și active inteligente. Un cont inteligent este un cont de utilizator obișnuit, dar pentru acesta este setat un script care controlează toate tranzacțiile. Un script de cont inteligent poate arăta astfel, de exemplu:

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

tx este o tranzacție în curs de procesare pe care o permitem utilizarea mecanismului de potrivire a modelelor numai dacă nu este o tranzacție de transfer. Potrivirea modelelor în RIDE este utilizată pentru a verifica tipul tranzacției. Toate conturile existente pot fi procesate în scriptul de cont inteligent tipuri de tranzacții.

De asemenea, scriptul poate declara variabile, poate folosi constructe „dacă-atunci-altfel” și alte metode pentru verificarea completă a condițiilor. Pentru a se asigura că contractele au caracter complet și complexitate (cost) demonstrabile, care este ușor de prezis înainte de a începe execuția contractului, RIDE nu conține bucle sau instrucțiuni de salt.

Alte caracteristici ale conturilor Waves includ prezența unei „stare”, adică starea contului. Puteți scrie un număr infinit de perechi (cheie, valoare) în starea contului folosind tranzacții de date (DataTransaction). Aceste informații pot fi apoi procesate atât prin API-ul REST, cât și direct în contractul inteligent.

Fiecare tranzacție poate conține o serie de dovezi, în care pot fi introduse semnătura participantului, ID-ul tranzacției necesare etc.

Lucrul cu RIDE prin IDE vă permite să vedeți vizualizarea compilată a contractului (dacă este compilat), să creați conturi noi și să setați scripturi pentru acesta, precum și să trimiteți tranzacții prin linia de comandă.

Pentru un ciclu complet, inclusiv crearea unui cont, instalarea unui contract inteligent pe acesta și trimiterea tranzacțiilor, puteți utiliza și o bibliotecă pentru a interacționa cu API-ul REST (de exemplu, C#, C, Java, JavaScript, Python, Rust, Elixir) . Pentru a începe să lucrați cu IDE-ul, faceți clic pe butonul NOU.

Posibilitățile de utilizare a contractelor inteligente sunt largi: de la interzicerea tranzacțiilor la anumite adrese („lista neagră”) până la dApps complexe.

Acum să ne uităm la exemple specifice de utilizare a contractelor inteligente în afaceri: atunci când se desfășoară licitații, asigurări și crearea de programe de loialitate.

Licitații

Una dintre condițiile pentru o licitație de succes este transparența: participanții trebuie să fie încrezători că este imposibil să manipulezi ofertele. Acest lucru poate fi realizat datorită blockchain-ului, unde datele imuabile despre toate pariurile și ora la care au fost efectuate vor fi disponibile pentru toți participanții.

Pe blockchain-ul Waves, ofertele pot fi înregistrate în starea contului de licitație prin DataTransaction.

De asemenea, puteți seta ora de începere și de încheiere a licitației folosind numere de bloc: frecvența de generare a blocurilor în blockchain-ul Waves este aproximativ egală cu 60 secunde.

1. Licitație cu preț crescător în engleză

Participanții la o licitație engleză pun ofertele în competiție între ei. Fiecare pariu nou trebuie să îl depășească pe cel anterior. Licitația se încheie atunci când nu mai există ofertanți care să depășească ultima ofertă. În acest caz, cel mai mare ofertant trebuie să furnizeze suma declarată.

Există și o opțiune de licitație în care vânzătorul stabilește un preț minim pentru lot, iar prețul final trebuie să îl depășească. În caz contrar, lotul rămâne nevândut.

În acest exemplu, lucrăm cu un cont creat special pentru licitație. Durata licitației este de 3000 de blocuri, iar prețul de pornire al lotului este de 0,001 VALURI. Un participant poate plasa o ofertă trimițând o DataTransaction cu cheia „preț” și valoarea ofertei sale.

Prețul noii oferte trebuie să fie mai mare decât prețul curent pentru această cheie, iar participantul trebuie să aibă cel puțin [new_bid + comision] token-uri în cont. Adresa ofertantului trebuie înregistrată în câmpul „expeditor” din DataTransaction, iar înălțimea curentă a blocului de oferte trebuie să fie în perioada licitației.

Dacă la sfârșitul licitației participantul a stabilit prețul cel mai mare, acesta poate trimite o Tranzacție de schimb pentru a plăti lotul corespunzător la prețul și perechea valutară specificate.

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. Licitație olandeză de prețuri în scădere

Într-o licitație olandeză, un lot este oferit inițial la un preț mai mare decât cel este dispus să plătească cumpărătorul. Prețul scade pas cu pas până când unul dintre participanți este de acord să cumpere lotul la prețul curent.

În acest exemplu folosim aceleași constante ca în cel precedent, precum și treapta de preț când delta scade. Scriptul contului verifică dacă participantul este într-adevăr primul care plasează un pariu. În caz contrar, DataTransaction nu este acceptată de blockchain.

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. Licitație „tot-pay”

„All-pay” este o licitație în care toți participanții plătesc oferta, indiferent de cine câștigă lotul. Fiecare nou participant plătește o ofertă, iar participantul care face oferta maximă câștigă lotul.

În exemplul nostru, fiecare participant la licitație plasează o ofertă prin DataTransaction cu (cheie, valoare)* = ("câștigător", adresă), ("preț", preț). O astfel de DataTransaction este aprobată numai dacă acest participant are deja o TransferTransaction cu semnătura sa și oferta sa este mai mare decât toate precedentele. Licitația continuă până când se atinge înălțimea finală.

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

Asigurare / Crowdfunding

Să luăm în considerare o situație în care trebuie să asigurați activele utilizatorilor împotriva pierderilor financiare. De exemplu, un utilizator dorește o garanție că, dacă un token se depreciază, va putea să recupereze întreaga sumă plătită pentru aceste token-uri și este dispus să plătească o sumă rezonabilă de asigurare.

Pentru a implementa acest lucru, trebuie emise „jetoane de asigurare”. Apoi este instalat un script în contul deținătorului poliței, permițând executarea doar acelor Tranzacții de schimb care îndeplinesc anumite condiții.

Pentru a preveni dublarea cheltuielilor, trebuie să solicitați utilizatorului să trimită o tranzacție de date în contul deținătorului poliței în prealabil cu (cheie, valoare) = (cumparareTransactionId, sellOrderId) și să interziceți trimiterea tranzacțiilor de date cu o cheie care a fost deja utilizată.

Prin urmare, dovezile utilizatorului trebuie să conțină ID-ul tranzacției pentru achiziționarea simbolului de asigurare. Perechea valutară trebuie să fie aceeași ca în tranzacția de cumpărare. Costul trebuie să fie, de asemenea, egal cu cel fixat la momentul achiziției, minus prețul asigurării.

Se înțelege că ulterior contul de asigurare cumpără de la utilizator jetoane de asigurare la un preț nu mai mic decât cel la care le-a achiziționat: contul de asigurare creează o Tranzacție de Schimb, utilizatorul semnează comanda (dacă tranzacția este finalizată corect), contul de asigurare semnează a doua comandă și întreaga tranzacție și o trimite către blockchain .

Dacă nu are loc nicio achiziție, utilizatorul poate crea o tranzacție Exchange în conformitate cu regulile descrise în script și poate trimite tranzacția către blockchain. În acest fel utilizatorul poate returna banii cheltuiți pentru achiziționarea de jetoane asigurate.

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

Un jeton de asigurare poate fi transformat într-un activ inteligent, de exemplu, pentru a interzice transferul lui către terți.

Această schemă poate fi implementată și pentru jetoanele de crowdfunding, care sunt returnate proprietarilor dacă suma necesară nu a fost colectată.

Taxe pe tranzacții

Contractele inteligente sunt aplicabile și în cazurile în care este necesară colectarea taxelor pentru fiecare tranzacție cu mai multe tipuri de active. Acest lucru se poate face printr-un nou activ cu instalat sponsorizare pentru tranzacții cu active inteligente:

1. Emitem FeeCoin, care va fi trimis utilizatorilor la un preț fix: 0,01 WAVES = 0,001 FeeCoin.

2. Setați sponsorizarea pentru FeeCoin și cursul de schimb: 0,001 WAVES = 0,001 FeeCoin.

3. Setați următorul script pentru activul inteligent:

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
}

Acum, de fiecare dată când cineva transferă N active inteligente, îți va oferi FeeCoin în valoare de N/taxDivisor (care poate fi achiziționat de la tine la 10 *N/taxDivisor WAVES), iar tu îi vei oferi minerului N/taxDivisor WAVES. Ca urmare, profitul (impozitul) dvs. va fi 9*N / taxDivisor WAVES.

De asemenea, puteți efectua impozitarea folosind un script de active inteligente ș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 programe de loialitate

Cashback este un tip de program de loialitate în care cumpărătorul primește înapoi o parte din suma cheltuită pentru un produs sau serviciu.

Când implementăm acest caz folosind un cont inteligent, trebuie să verificăm dovezile în același mod ca și în cazul asigurării. Pentru a preveni dublarea cheltuielilor, utilizatorul trebuie să trimită o DataTransaction cu (cheie, valoare) = (purchaseTransactionId, cashbackTransactionId) înainte de a primi cashback.

De asemenea, trebuie să stabilim o interdicție a cheilor existente folosind DataTransaction. cashbackDivisor - unitate împărțită la cota cashback. Acestea. dacă cota cashback este 0.1, atunci 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)
}

Schimb atomic

Schimbul atomic permite utilizatorilor să schimbe active fără ajutorul unui schimb. În cazul unui swap atomic, ambii participanți la tranzacție trebuie să îl confirme într-o anumită perioadă de timp.

Dacă cel puțin unul dintre participanți nu oferă confirmarea corectă a tranzacției în timpul alocat tranzacției, tranzacția este anulată și schimbul nu are loc.

În exemplul nostru, vom folosi următorul script de cont inteligent:

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
}

În articolul următor ne vom uita la utilizarea conturilor inteligente în instrumente financiare, cum ar fi opțiuni, contracte futures și facturi.

Sursa: www.habr.com

Adauga un comentariu