Waves'i nutikate kontode rakendamine: oksjonitest boonusprogrammideni

Waves'i nutikate kontode rakendamine: oksjonitest boonusprogrammideni

Blockchaini seostatakse sageli ainult krüptorahadega, kuid DLT-tehnoloogia rakendusalad on palju laiemad. Üks paljutõotavamaid valdkondi plokiahela kasutamisel on nutikas leping, mis täidetakse automaatselt ja ei nõua usaldust selle sõlminud osapoolte vahel.

RIDE – nutikate lepingute keel

Waves on nutikate lepingute jaoks välja töötanud spetsiaalse keele – RIDE. Selle täielik dokumentatsioon asub siin. Ja siin - artikkel sellel teemal kohta Habr.

RIDE leping on predikaat ja tagastab väljundina "tõene" või "väär". Vastavalt sellele tehing kas salvestatakse plokiahelasse või lükatakse tagasi. Nutileping tagab täielikult kindlaksmääratud tingimuste täitmise. Lepingust tehingute genereerimine RIDE-s ei ole hetkel võimalik.

Tänapäeval on kahte tüüpi Waves'i nutikaid lepinguid: nutikad kontod ja nutikad varad. Nutikonto on tavaline kasutajakonto, kuid sellele on seatud skript, mis kontrollib kõiki tehinguid. Nutikas konto skript võib välja näha näiteks selline:

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

tx on töödeldav tehing, mille puhul lubame mustrite sobitamise mehhanismi kasutada ainult juhul, kui tegemist ei ole ülekandetehinguga. Mustri sobitamist RIDE-s kasutatakse tehingu tüübi kontrollimiseks. Kõiki olemasolevaid kontosid saab töödelda nutika konto skriptis tehingutüübid.

Skript võib ka deklareerida muutujaid, kasutada "kui-siis-muu" konstruktsioone ja muid meetodeid tingimuste täielikuks kontrollimiseks. Tagamaks, et lepingutel on tõestatav täielikkus ja keerukus (kulu), mida on enne lepingu täitmise algust lihtne ennustada, ei sisalda RIDE silmuseid ega hüppelauseid.

Muud Waves'i kontode funktsioonid hõlmavad oleku, st konto oleku olemasolu. Andmetehingute (DataTransaction) abil saate konto olekusse kirjutada lõpmatu arvu paare (võti, väärtus). Seda teavet saab seejärel töödelda nii REST API kaudu kui ka otse nutikas lepingus.

Iga tehing võib sisaldada massiivi tõendeid, millesse saab sisestada osaleja allkirja, vajaliku tehingu ID jne.

Töötamine rakendusega RIDE via IDE võimaldab näha lepingu koostatud vaadet (kui see on koostatud), luua uusi kontosid ja määrata sellele skripte, samuti saata tehinguid käsurea kaudu.

Täistsükli jooksul, sealhulgas konto loomine, sellele nutika lepingu installimine ja tehingute saatmine, saate REST API-ga suhtlemiseks kasutada ka teeki (näiteks C#, C, Java, JavaScript, Python, Rust, Elixir) . IDE-ga töötamise alustamiseks klõpsake lihtsalt nuppu UUS.

Nutikate lepingute kasutamise võimalused on laiad: tehingute keelamisest teatud aadressidele (“must nimekiri”) kuni keerukate dAppideni.

Vaatame nüüd konkreetseid näiteid nutikate lepingute kasutamisest ettevõtluses: oksjonite läbiviimisel, kindlustusel ja lojaalsusprogrammide loomisel.

Oksjonid

Eduka oksjoni üks tingimus on läbipaistvus: osalejad peavad olema kindlad, et pakkumistega on võimatu manipuleerida. Seda on võimalik saavutada tänu plokiahelale, kus muutumatud andmed kõigi panuste ja nende tegemise aja kohta on kõigile osalejatele kättesaadavad.

Waves'i plokiahelas saab pakkumisi salvestada oksjonikonto olekus DataTransactioni kaudu.

Oksjoni algus- ja lõpuaega saab määrata ka plokinumbrite abil: ploki genereerimise sagedus Waves'i plokiahelas on ligikaudu võrdne 60 sekundit.

1. Inglise tõusva hinnaga oksjon

Inglise oksjonil osalejad teevad üksteisega konkureerivaid pakkumisi. Iga uus panus peab ületama eelmist. Oksjon lõppeb, kui viimast pakkumist ületavate pakkujate seast enam ei ole. Sel juhul peab kõrgeima pakkumise tegija esitama märgitud summa.

Samuti on oksjonivõimalus, kus müüja määrab osale miinimumhinna ja lõpphind peab seda ületama. Muidu jääb partii müümata.

Selles näites töötame spetsiaalselt oksjoni jaoks loodud kontoga. Oksjoni kestus on 3000 plokki ja partii alghind 0,001 LAINET. Osaleja saab teha pakkumise, saates DataTransactioni võtme “hind” ja oma pakkumise väärtusega.

Uue pakkumise hind peab olema kõrgem selle võtme praegusest hinnast ja osaleja kontol peab olema vähemalt [uus_pakkumine + vahendustasu] märgid. Pakkuja aadress peab olema salvestatud DataTransactioni väljale "saatja" ja hetke pakkumisploki kõrgus peab jääma oksjoniperioodi.

Kui osaleja on oksjoni lõpus määranud kõrgeima hinna, saab ta saata Börsitehingu vastava partii eest tasumiseks määratud hinna ja valuutapaariga.

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. Hollandi langevate hindade oksjon

Hollandi oksjonil pakutakse algselt palju kõrgema hinnaga, kui ostja on nõus maksma. Hinda alandatakse samm-sammult, kuni üks osalejatest on nõus partii praeguse hinnaga ostma.

Selles näites kasutame samu konstante, mis eelmises, samuti hinnasammu, kui delta väheneb. Konto skript kontrollib, kas osaleja on tõesti esimene, kes panuse teeb. Vastasel juhul ei aktsepteeri plokiahel DataTransactionit.

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. Oksjon "kõik maksab"

“All-pay” on oksjon, kus kõik osalejad maksavad pakkumise, olenemata sellest, kes loosi võidab. Iga uus osaleja maksab pakkumise ja loosi võidab maksimaalse pakkumise teinud osaleja.

Meie näites teeb iga oksjonil osaleja DataTransactioni kaudu pakkumise (võti, väärtus)* = (“võitja”, aadress),(“hind”, hind). Selline DataTransaction kinnitatakse ainult siis, kui sellel osalejal on juba allkirjaga TransferTransaction ja tema pakkumine on kõrgem kui kõik eelnevad. Oksjon kestab kuni saavutatakse lõppKõrgus.

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

Kindlustus / ühisrahastus

Vaatleme olukorda, kus peate kindlustama kasutajate vara rahalise kahju vastu. Näiteks soovib kasutaja garantiid, et kui žetoon langeb, saab ta tagasi kogu nende žetoonide eest makstud summa, ja on nõus tasuma mõistliku summa kindlustust.

Selle rakendamiseks tuleb väljastada kindlustusmärgid. Seejärel installitakse kindlustusvõtja kontole skript, mis võimaldab teostada ainult teatud tingimustele vastavaid vahetustehinguid.

Topeltkulutamise vältimiseks tuleb kasutajalt nõuda DataTransactioni saatmist kindlustusvõtja kontole eelnevalt koos (võti, väärtus) = (purchaseTransactionId, sellOrderId) ja keelata DataTransactioni saatmine juba kasutatud võtmega.

Seetõttu peavad kasutaja tõendid sisaldama kindlustusžetoonide ostu tehingu ID-d. Valuutapaar peab olema sama, mis ostutehingus. Samuti peab kulu olema võrdne ostuhetkel fikseeritud kuluga, millest on lahutatud kindlustuse hind.

Arusaadavalt ostab kindlustuskonto hiljem kasutajalt kindlustusžetoone hinnaga, mis ei ole madalam kui see, millega ta need ostis: kindlustuskonto loob vahetustehingu, kasutaja allkirjastab tellimuse (kui tehing on õigesti sooritatud), kindlustuskonto allkirjastab teise tellimuse ja kogu tehingu ning saadab selle plokiahelasse.

Kui ostu ei toimu, saab kasutaja luua ExchangeTransactioni vastavalt skriptis kirjeldatud reeglitele ja saata tehingu plokiahelasse. Nii saab kasutaja kindlustatud žetoonide ostmiseks kulutatud raha tagastada.

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

Kindlustusmärgi saab teha nutikaks varaks, näiteks keelata selle üleandmine kolmandatele isikutele.

Seda skeemi saab rakendada ka ühisrahastuse žetoonide puhul, mis tagastatakse omanikele, kui vajalikku summat pole kogutud.

Tehingumaksud

Nutikad lepingud on rakendatavad ka juhtudel, kui on vaja koguda maksu igalt tehingult mitut liiki varadega. Seda saab teha installitud uue vara kaudu sponsorlus nutikate varadega tehtavate tehingute puhul:

1. Väljastame FeeCoini, mis saadetakse kasutajatele fikseeritud hinnaga: 0,01 LAINET = 0,001 FeeCoin.

2. Määrake FeeCoini sponsorlus ja vahetuskurss: 0,001 LAINET = 0,001 FeeCoini.

3. Määrake nutika vara jaoks järgmine skript:

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
}

Nüüd iga kord, kui keegi kannab üle N nutikat vara, annab ta teile FeeCoini summas N/taxDivisor (mida saab teilt osta hinnaga 10 *N/taxDivisor WAVES) ja teie annate kaevandajale N/taxDivisor WAVES. Selle tulemusena on teie kasum (maks) 9*N / taxDivisor WAVES.

Samuti saate maksustada nutika vara skripti ja MassTransferTransactioni abil:

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 lojaalsusprogrammid

Cashback on teatud tüüpi lojaalsusprogramm, mille puhul ostja saab osa tootele või teenusele kulutatud summast tagasi.

Selle juhtumi juurutamisel nutikonto abil peame kontrollima tõendeid samamoodi nagu kindlustusjuhtumi puhul. Topeltkulutamise vältimiseks peab kasutaja enne raha tagasi saamist saatma DataTransactioni (võti, väärtus) = (purchaseTransactionId, cashbackTransactionId).

Samuti peame seadma keelu olemasolevatele võtmetele, kasutades DataTransactionit. cashback Divisor – üksus jagatud cashbacki osaga. Need. kui cashbacki osakaal on 0.1, siis cashbacki jagaja 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)
}

Aatomi vahetus

Aatomivahetus võimaldab kasutajatel vahetada varasid ilma vahetust abita. Aatomivahetuse puhul peavad mõlemad tehingus osalejad selle teatud aja jooksul kinnitama.

Kui vähemalt üks osalejatest ei esita tehingule määratud aja jooksul õiget kinnitust tehingu kohta, siis tehing tühistatakse ja vahetust ei toimu.

Meie näites kasutame järgmist nutika konto skripti:

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
}

Järgmises artiklis vaatleme nutikate kontode kasutamist sellistes finantsinstrumentides nagu optsioonid, futuurid ja arved.

Allikas: www.habr.com

Lisa kommentaar