„Waves“ išmaniųjų paskyrų taikymas: nuo aukcionų iki premijų programų

„Waves“ išmaniųjų paskyrų taikymas: nuo aukcionų iki premijų programų

„Blockchain“ dažnai siejama tik su kriptovaliutomis, tačiau DLT technologijos pritaikymo sritys yra daug platesnės. Viena perspektyviausių „blockchain“ naudojimo sričių – išmanioji sutartis, vykdoma automatiškai ir nereikalaujanti pasitikėjimo tarp ją sudariusių šalių.

RIDE – išmaniųjų sutarčių kalba

Waves sukūrė specialią kalbą išmaniosioms sutartims – RIDE. Yra visa jo dokumentacija čia. Ir čia - straipsnis šia tema apie Habr.

RIDE sutartis yra predikatas ir kaip išvestį pateikia „true“ arba „false“. Atitinkamai, sandoris įrašomas į blokų grandinę arba atmetamas. Išmanioji sutartis visiškai garantuoja nurodytų sąlygų įvykdymą. Šiuo metu negalima generuoti operacijų iš sutarties RIDE.

Šiandien yra dviejų tipų „Waves“ išmaniosios sutartys: išmaniosios paskyros ir išmanusis turtas. Išmanioji paskyra yra įprasta vartotojo paskyra, tačiau jai yra nustatytas scenarijus, valdantis visas operacijas. Išmaniosios paskyros scenarijus gali atrodyti taip, pavyzdžiui:

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

tx yra apdorojama operacija, kurią leidžiame naudoti šablonų atitikimo mechanizmą tik tuo atveju, jei tai nėra perdavimo operacija. Modelio atitikimas RIDE naudojamas operacijos tipui patikrinti. Visos esamos paskyros gali būti apdorojamos naudojant išmaniosios paskyros scenarijų sandorių tipai.

Scenarijus taip pat gali deklaruoti kintamuosius, naudoti „if-then-else“ konstrukcijas ir kitus metodus, kad būtų galima visiškai patikrinti sąlygas. Siekiant užtikrinti, kad sutarčių išsamumas ir sudėtingumas (išlaidos) būtų įrodomas, o tai būtų lengva numatyti prieš pradedant vykdyti sutartį, RIDE nėra kilpų ar šuolio teiginių.

Kitos „Waves“ paskyrų funkcijos apima „būsenos“ buvimą, tai yra, paskyros būseną. Naudodami duomenų operacijas (DataTransaction) į sąskaitos būseną galite įrašyti begalinį skaičių porų (raktas, reikšmė). Tada ši informacija gali būti apdorojama tiek per REST API, tiek tiesiogiai išmaniojoje sutartyje.

Kiekvienoje operacijoje gali būti masyvas įrodymų, į kuriuos galima įvesti dalyvio parašą, reikiamos operacijos ID ir pan.

Darbas su RIDE per IDE leidžia matyti sudarytą sutarties vaizdą (jei jis sudarytas), kurti naujas paskyras ir nustatyti jai scenarijus, taip pat siųsti operacijas per komandinę eilutę.

Visą ciklą, įskaitant paskyros sukūrimą, išmaniosios sutarties įdiegimą ir operacijų siuntimą, taip pat galite naudoti biblioteką sąveikai su REST API (pavyzdžiui, C#, C, Java, JavaScript, Python, Rust, Elixir) . Norėdami pradėti dirbti su IDE, tiesiog spustelėkite mygtuką NAUJAS.

Išmaniųjų sutarčių naudojimo galimybės yra plačios: nuo sandorių uždraudimo tam tikrais adresais („juodasis sąrašas“) iki sudėtingų „dApps“.

Dabar pažvelkime į konkrečius išmaniųjų sutarčių panaudojimo versle pavyzdžius: vykdant aukcionus, draudimą ir kuriant lojalumo programas.

Aukcionai

Viena iš sėkmingo aukciono sąlygų yra skaidrumas: dalyviai turi būti tikri, kad pasiūlymais manipuliuoti neįmanoma. Tai galima pasiekti naudojant blokų grandinę, kurioje visiems dalyviams bus prieinami nekintantys duomenys apie visus statymus ir jų atlikimo laiką.

„Waves“ blokų grandinėje pasiūlymai gali būti įrašyti aukciono paskyros būsenoje naudojant „DataTransaction“.

Aukciono pradžios ir pabaigos laiką taip pat galite nustatyti naudodami blokų numerius: blokų generavimo dažnis „Waves“ blokų grandinėje yra maždaug lygus 60 sekundžių.

1. Angliškas aukcionas didėjančia kaina

Dalyviai Anglijos aukcione konkuruoja tarpusavyje. Kiekvienas naujas statymas turi viršyti ankstesnįjį. Aukcionas baigiasi, kai nebelieka dalyvių, kurie viršytų paskutinį pasiūlymą. Tokiu atveju didžiausią kainą pasiūlęs asmuo turi pateikti nurodytą sumą.

Taip pat yra aukciono variantas, kai pardavėjas nustato minimalią sklypo kainą, o galutinė kaina turi ją viršyti. Priešingu atveju aikštelė lieka neparduota.

Šiame pavyzdyje dirbame su paskyra, specialiai sukurta aukcionui. Aukciono trukmė – 3000 blokų, o pradinė sklypo kaina – 0,001 BANGOS. Dalyvis gali pateikti pasiūlymą atsiųsdamas DataTransaction raktą „kaina“ ir savo pasiūlymo vertę.

Naujo pasiūlymo kaina turi būti didesnė už dabartinę šio rakto kainą, o dalyvis savo paskyroje turi turėti bent [naujas_siūlymas + komisinis] žetonų. Dalyvio adresas turi būti įrašytas „DataTransaction“ laukelyje „siuntėjas“, o esamas pasiūlymų bloko aukštis turi būti aukciono laikotarpiu.

Jei aukciono pabaigoje dalyvis nustatė aukščiausią kainą, jis gali išsiųsti ExchangeTransaction, kad apmokėtų už atitinkamą lotą nurodyta kaina ir valiutų pora.

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. Nyderlandų mažėjančių kainų aukcionas

Olandijos aukcione iš pradžių daug kas siūloma už didesnę kainą, nei pirkėjas nori mokėti. Kaina mažinama žingsnis po žingsnio, kol vienas iš dalyvių sutinka pirkti sklypą už esamą kainą.

Šiame pavyzdyje naudojame tas pačias konstantas kaip ir ankstesniame, taip pat kainos žingsnį, kai delta mažėja. Sąskaitos scenarijus patikrina, ar dalyvis tikrai pirmasis atlieka statymą. Priešingu atveju blokų grandinė nepriima DataTransaction.

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. Aukcionas „visas mokėjimas“

„All-pay“ yra aukcionas, kuriame visi dalyviai sumoka kainą, nepaisant to, kas laimės lotą. Kiekvienas naujas dalyvis moka pasiūlymą, o didžiausią pasiūlymą pasiūlęs dalyvis laimi lotą.

Mūsų pavyzdyje kiekvienas aukciono dalyvis pateikia pasiūlymą per „DataTransaction“ su (raktas, vertė)* = („laimėtojas“, adresas), („kaina“, kaina). Toks DataTransaction patvirtinamas tik tuo atveju, jei šis dalyvis jau turi TransferTransaction su savo parašu ir jo pasiūlymas yra didesnis nei visų ankstesnių. Aukcionas tęsiasi tol, kol pasiekiamas 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)
}

Draudimas / sutelktinis finansavimas

Panagrinėkime situaciją, kai reikia apdrausti vartotojų turtą nuo finansinių nuostolių. Pavyzdžiui, vartotojas nori garantijos, kad jei žetonas nuvertės, jis galės susigrąžinti visą už šiuos žetonus sumokėtą sumą ir yra pasirengęs mokėti protingą draudimo sumą.

Norint tai įgyvendinti, reikia išduoti „draudimo žetonus“. Tada draudėjo paskyroje įdiegiamas scenarijus, leidžiantis vykdyti tik tas ExchangeTransactions, kurios atitinka tam tikras sąlygas.

Norėdami išvengti dvigubų išlaidų, turite iš anksto paprašyti vartotojo išsiųsti DataTransaction į draudėjo sąskaitą su (raktas, vertė) = (purchaseTransactionId, sellOrderId) ir uždrausti siųsti DataTransactions su raktu, kuris jau buvo naudojamas.

Todėl vartotojo įrodymuose turi būti draudimo žetono pirkimo operacijos ID. Valiutų pora turi būti tokia pati kaip pirkimo operacijoje. Kaina taip pat turi būti lygi pirkimo metu nustatytai kainai, atėmus draudimo kainą.

Suprantama, kad vėliau draudimo sąskaita iš vartotojo perka draudimo žetonus už ne mažesnę nei ta, už kurią jis juos įsigijo, kainą: draudimo sąskaita sukuria Exchange operaciją, vartotojas pasirašo pavedimą (jei operacija atlikta teisingai), draudimo sąskaita pasirašo antrą pavedimą ir visą operaciją bei siunčia ją į blokų grandinę.

Jei neįvyksta pirkimo, vartotojas gali sukurti ExchangeTransaction pagal scenarijuje aprašytas taisykles ir išsiųsti operaciją į blokų grandinę. Tokiu būdu vartotojas gali grąžinti pinigus, išleistus apdraustų žetonų pirkimui.

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

Draudimo žetoną galima padaryti išmaniuoju turtu, pavyzdžiui, uždrausti jį perduoti trečiosioms šalims.

Šią schemą galima įgyvendinti ir sutelktinio finansavimo žetonams, kurie, nesurinkus reikiamos sumos, grąžinami savininkams.

Sandorių mokesčiai

Išmaniosios sutartys taikomos ir tais atvejais, kai reikia rinkti mokestį už kiekvieną sandorį su kelių rūšių turtu. Tai galima padaryti įdiegus naują turtą rėmimas operacijoms su išmaniuoju turtu:

1. Išleidžiame FeeCoin, kuris bus išsiųstas vartotojams už fiksuotą kainą: 0,01 BANGOS = 0,001 FeeCoin.

2. Nustatykite FeeCoin rėmimą ir valiutos kursą: 0,001 BANGOS = 0,001 FeeCoin.

3. Nustatykite šį išmaniojo išteklių scenarijų:

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
}

Dabar kiekvieną kartą, kai kas nors perves N išmanųjį turtą, jums duos FeeCoin N/taxDivisor suma (kurią iš jūsų galite įsigyti už 10 *N/taxDivisor WAVES), o jūs duosite kalnakasiui N/taxDivisor WAVES. Dėl to jūsų pelnas (mokestis) bus 9*N / taxDivisor WAVES.

Taip pat galite atlikti apmokestinimą naudodami išmanųjį turto scenarijų ir 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
}

Grynųjų pinigų grąžinimo ir lojalumo programos

Cashback yra lojalumo programos rūšis, kai pirkėjas atgauna dalį sumos, išleistos prekei ar paslaugai.

Diegdami šį atvejį naudodamiesi išmaniąja paskyra, įrodymus turime patikrinti taip pat, kaip ir draudimo atveju. Kad išvengtų dvigubų išlaidų, vartotojas turi išsiųsti DataTransaction su (raktas, vertė) = (purchaseTransactionId, cashbackTransactionId), prieš gaudamas pinigų grąžinimą.

Taip pat turime uždrausti esamus raktus naudojant „DataTransaction“. cashbackDivisor – vienetas, padalintas iš pinigų grąžinimo dalies. Tie. jei pinigų grąžinimo dalis yra 0.1, tada pinigų grąžinimo daliklis 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)
}

Atominis apsikeitimas

Atominis apsikeitimas leidžia vartotojams keistis turtu be mainų pagalbos. Atliekant atominį apsikeitimą, abu sandorio dalyviai turi tai patvirtinti per tam tikrą laikotarpį.

Jei bent vienas iš dalyvių per sandoriui skirtą laiką nepateikia teisingo sandorio patvirtinimo, sandoris anuliuojamas ir mainai neįvyksta.

Mūsų pavyzdyje naudosime šį išmaniosios paskyros scenarijų:

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
}

Kitame straipsnyje apžvelgsime išmaniųjų sąskaitų naudojimą finansinėse priemonėse, tokiose kaip opcionai, ateities sandoriai ir vekseliai.

Šaltinis: www.habr.com

Добавить комментарий