Waves-älytilien sovellukset: huutokaupoista bonusohjelmiin

Waves-älytilien sovellukset: huutokaupoista bonusohjelmiin

Blockchain liitetään usein vain kryptovaluuttoihin, mutta DLT-teknologian sovellusalueet ovat paljon laajempia. Yksi lupaavimmista lohkoketjun käytön alueista on älykäs sopimus, joka toteutuu automaattisesti ja joka ei vaadi luottamusta siihen solmineiden osapuolten välillä.

RIDE – älykkäiden sopimusten kieli

Waves on kehittänyt älykkäille sopimuksille erityisen kielen – RIDE. Sen täydellinen dokumentaatio löytyy täällä. Ja täällä - artikkeli tästä aiheesta osoitteessa Habr.

RIDE-sopimus on predikaatti ja palauttaa "true" tai "false" tulosteena. Vastaavasti tapahtuma joko kirjataan lohkoketjuun tai hylätään. Älykäs sopimus takaa täysin määriteltyjen ehtojen täyttymisen. Tapahtuman luominen sopimuksesta RIDEssä ei ole tällä hetkellä mahdollista.

Nykyään Waves-älysopimuksia on kahdenlaisia: älykkäät tilit ja älykkäät omaisuuserät. Älykäs tili on tavallinen käyttäjätili, mutta sille on asetettu komentosarja, joka ohjaa kaikkia tapahtumia. Älykäs tilin komentosarja voi näyttää esimerkiksi tältä:

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

tx on käsiteltävänä oleva tapahtuma, jonka sallimme käyttää mallinsovitusmekanismia vain, jos se ei ole siirtotapahtuma. RIDE-kuvion täsmäämistä käytetään tapahtumatyypin tarkistamiseen. Kaikki olemassa olevat tilit voidaan käsitellä älytilin komentosarjassa tapahtumatyypit.

Komentosarja voi myös ilmoittaa muuttujia, käyttää "if-then-else" -konstrukteja ja muita menetelmiä olosuhteiden täydelliseen tarkistamiseen. Varmistaakseen, että sopimuksilla on todistettavissa oleva täydellisyys ja monimutkaisuus (kustannus), joka on helppo ennustaa ennen sopimuksen täytäntöönpanon alkamista, RIDE ei sisällä silmukoita tai hyppylauseita.

Waves-tilien muita ominaisuuksia ovat "tila" eli tilin tila. Voit kirjoittaa tilin tilaan äärettömän määrän pareja (avain, arvo) käyttämällä datatapahtumia (DataTransaction). Nämä tiedot voidaan sitten käsitellä sekä REST API:n kautta että suoraan älykkäässä sopimuksessa.

Jokainen tapahtuma voi sisältää joukon todisteita, joihin voidaan syöttää osallistujan allekirjoitus, vaaditun tapahtuman tunnus jne.

Työskentely RIDE:n kautta IDE voit nähdä kootun näkymän sopimuksesta (jos se on käännetty), luoda uusia tilejä ja asettaa sille skriptejä sekä lähettää tapahtumia komentorivin kautta.

Koko syklin ajan, mukaan lukien tilin luominen, älysopimuksen asentaminen siihen ja tapahtumien lähettäminen, voit myös käyttää kirjastoa vuorovaikutukseen REST API:n kanssa (esimerkiksi C#, C, Java, JavaScript, Python, Rust, Elixir) . Aloita työskentely IDE:n kanssa napsauttamalla NEW-painiketta.

Älykkäiden sopimusten käyttömahdollisuudet ovat laajat: tiettyihin osoitteisiin tapahtuvien tapahtumien kieltämisestä ("musta lista") monimutkaisiin dApppeihin.

Katsotaan nyt konkreettisia esimerkkejä älykkäiden sopimusten käytöstä liiketoiminnassa: huutokaupoissa, vakuutuksissa ja kanta-asiakasohjelmien luomisessa.

Huutokaupat

Yksi onnistuneen huutokaupan edellytyksistä on avoimuus: osallistujien on oltava varmoja siitä, että tarjouksia on mahdotonta manipuloida. Tämä voidaan saavuttaa lohkoketjun ansiosta, jossa muuttumaton tieto kaikista vedoista ja niiden tekoajasta on kaikkien osallistujien saatavilla.

Waves-lohkoketjussa tarjoukset voidaan tallentaa huutokauppatilin tilaan DataTransactionin kautta.

Voit myös asettaa huutokaupan alkamis- ja päättymisajan lohkonumeroiden avulla: lohkon generointitiheys Waves-lohkoketjussa on suunnilleen sama kuin 60 sekuntia.

1. Englantilainen nousevan hinnan huutokauppa

Englanninkielisen huutokaupan osallistujat kilpailevat keskenään. Jokaisen uuden panoksen on oltava suurempi kuin edellinen. Huutokauppa päättyy, kun viimeistä tarjousta ylittäviä tarjoajia ei ole enää. Tässä tapauksessa korkeimman tarjouksen tehneen on toimitettava ilmoitettu summa.

Tarjolla on myös huutokauppavaihtoehto, jossa myyjä asettaa erälle vähimmäishinnan ja lopullisen hinnan tulee ylittää se. Muuten erä jää myymättä.

Tässä esimerkissä työskentelemme huutokauppaa varten luodun tilin kanssa. Huutokaupan kesto on 3000 kappaletta ja erän lähtöhinta on 0,001 AALTOA. Osallistuja voi tehdä tarjouksen lähettämällä DataTransactionin avaimella "hinta" ja tarjouksensa arvolla.

Uuden tarjouksen hinnan on oltava korkeampi kuin tämän avaimen nykyinen hinta, ja osallistujalla on oltava tilillään vähintään [uusi_tarjous + provisio] tokeneita. Tarjoajan osoite on kirjattava DataTransactionin "lähettäjä"-kenttään ja nykyisen tarjouslohkon korkeuden on oltava huutokauppajakson sisällä.

Jos osallistuja on huutokaupan lopussa asettanut korkeimman hinnan, hän voi lähettää Vaihtotapahtuman maksaakseen vastaavan erän määritellyllä hinnalla ja valuuttaparilla.

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. Alankomaiden huutokauppa laskevista hinnoista

Hollantilaisessa huutokaupassa erä tarjotaan aluksi korkeammalla hinnalla kuin mitä ostaja on valmis maksamaan. Hinta laskee asteittain, kunnes joku osallistujista suostuu ostamaan kohteen nykyiseen hintaan.

Tässä esimerkissä käytämme samoja vakioita kuin edellisessä, samoin kuin hintaaskelmaa, kun delta pienenee. Tiliohjelma tarkistaa, onko osallistuja todellakin ensimmäinen, joka asettaa vedon. Muussa tapauksessa lohkoketju ei hyväksy DataTransactionia.

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. Huutokauppa "kaikki maksaa"

"All-pay" on huutokauppa, jossa kaikki osallistujat maksavat tarjouksen riippumatta siitä, kuka voittaa. Jokainen uusi osallistuja maksaa tarjouksen, ja suurimman tarjouksen tehnyt osallistuja voittaa.

Esimerkissämme jokainen huutokauppaan osallistuja tekee tarjouksen DataTransactionin kautta, jossa (avain, arvo)* = ("voittaja", osoite),("hinta", hinta). Tällainen DataTransaction hyväksytään vain, jos tällä osallistujalla on jo allekirjoitettu TransferTransaction ja hänen tarjouksensa on korkeampi kuin kaikki aiemmat. Huutokauppa jatkuu, kunnes EndHeight on saavutettu.

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

Vakuutus / Joukkorahoitus

Tarkastellaan tilannetta, jossa käyttäjien omaisuus on vakuutettava taloudellisia menetyksiä vastaan. Käyttäjä haluaa esimerkiksi takuun siitä, että jos rahakkeen arvo heikkenee, hän voi saada takaisin näistä tokeneista maksetun summan kokonaisuudessaan ja on valmis maksamaan kohtuullisen summan vakuutusta.

Tämän toteuttamiseksi on myönnettävä "vakuutustunnukset". Tämän jälkeen vakuutuksenottajan tilille asennetaan komentosarja, joka sallii vain tietyt ehdot täyttävät Vaihtotapahtumat.

Kaksinkertaisen kulutuksen estämiseksi sinun tulee pyytää käyttäjää lähettämään DataTransaction etukäteen vakuutuksenottajan tilille komennolla (avain, arvo) = (purchaseTransactionId, sellOrderId) ja kieltää DataTransactionien lähettäminen jo käytetyllä avaimella.

Siksi käyttäjän todisteissa on oltava vakuutustunnuksen ostotapahtumatunnus. Valuuttaparin on oltava sama kuin ostotapahtumassa. Kustannusten on myös oltava yhtä suuri kuin ostohetkellä vahvistettu hinta, josta on vähennetty vakuutuksen hinta.

On selvää, että vakuutustili ostaa myöhemmin käyttäjältä vakuutustokeneita hintaan, joka ei ole alempi kuin se, jolla hän ne osti: vakuutustili luo Vaihtotapahtuman, käyttäjä allekirjoittaa tilauksen (jos tapahtuma on suoritettu oikein), vakuutustili allekirjoittaa toisen tilauksen ja koko tapahtuman ja lähettää sen lohkoketjuun.

Jos ostoa ei tapahdu, käyttäjä voi luoda ExchangeTransactionin skriptissä kuvattujen sääntöjen mukaisesti ja lähettää tapahtuman lohkoketjuun. Näin käyttäjä voi palauttaa vakuutettujen tokenien ostoon käyttämänsä rahat.

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

Vakuutustunnuksesta voidaan tehdä älykäs omaisuus esimerkiksi estämään sen siirtäminen kolmansille osapuolille.

Tätä järjestelmää voidaan toteuttaa myös joukkorahoitustokeneille, jotka palautetaan omistajille, mikäli vaadittua summaa ei ole kerätty.

Transaktioverot

Älykkäitä sopimuksia voidaan soveltaa myös silloin, kun on tarpeen periä veroa jokaisesta kaupasta useiden erilaisten omaisuuserien kanssa. Tämä voidaan tehdä uudella omaisuudella, joka on asennettu sponsorointi älykkäillä resursseilla tehdyt tapahtumat:

1. Myönnämme FeeCoinin, joka lähetetään käyttäjille kiinteään hintaan: 0,01 AALTOA = 0,001 FeeCoin.

2. Aseta sponsorointi FeeCoinille ja vaihtokurssi: 0,001 AALLOT = 0,001 FeeCoin.

3. Aseta seuraava komentosarja älykkäälle omaisuudelle:

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
}

Nyt joka kerta, kun joku siirtää N älykästä omaisuutta, hän antaa sinulle FeeCoinin N/taxDivisorin määrässä (jotka voit ostaa sinulta hintaan 10 *N/taxDivisor WAVES), ja sinä annat kaivostyöläiselle N/taxDivisor WAVES. Tämän seurauksena voittosi (vero) on 9*N / taxDivisor WAVES.

Voit myös suorittaa verotuksen käyttämällä älykästä omaisuuskomentosarjaa ja MassTransferTransactionia:

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- ja kanta-asiakasohjelmat

Cashback on eräänlainen kanta-asiakasohjelma, jossa ostaja saa takaisin osan tuotteeseen tai palveluun käytetystä summasta.

Toteutettaessa tätä tapausta älytilillä meidän tulee tarkistaa todisteet samalla tavalla kuin teimme vakuutustapauksessa. Kaksinkertaisen kulutuksen estämiseksi käyttäjän on lähetettävä DataTransaction (avain, arvo) = (purchaseTransactionId, cashbackTransactionId) ennen cashbackin vastaanottamista.

Meidän on myös asetettava kielto olemassa oleville avaimille käyttämällä DataTransactionia. cashbackDivisor - yksikkö jaettuna cashback-osuudella. Nuo. jos cashback-osuus on 0.1, niin cashback-jakaja 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)
}

Atomin vaihto

Atomic swap -palvelun avulla käyttäjät voivat vaihtaa omaisuutta ilman vaihdon apua. Atomiswapissa molempien transaktion osallistujien on vahvistettava se tietyn ajan kuluessa.

Jos vähintään yksi osallistujista ei anna oikeaa vahvistusta tapahtumasta tapahtumalle varatun ajan kuluessa, tapahtuma peruuntuu eikä vaihtoa tapahdu.

Esimerkissämme käytämme seuraavaa älykkään tilin komentosarjaa:

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
}

Seuraavassa artikkelissa tarkastellaan älykkäiden tilien käyttöä rahoitusvälineissä, kuten optioissa, futuureissa ja laskuissa.

Lähde: will.com

Lisää kommentti