Waves viedo kontu lietojumprogrammas: no izsolēm līdz bonusu programmām

Waves viedo kontu lietojumprogrammas: no izsolēm līdz bonusu programmām

Blockchain bieži asociējas tikai ar kriptovalūtām, taču DLT tehnoloģijas pielietojuma jomas ir daudz plašākas. Viena no perspektīvākajām blokķēdes izmantošanas jomām ir viedais līgums, kas tiek izpildīts automātiski un neprasa uzticību starp pusēm, kas to noslēdza.

RIDE – viedo līgumu valoda

Waves ir izstrādājis īpašu valodu viedajiem līgumiem – RIDE. Tā pilnīga dokumentācija atrodas šeit. Un šeit - rakstu par šo tēmu uz Habr.

RIDE līgums ir predikāts un atgriež “true” vai “false” kā izvadi. Attiecīgi darījums tiek vai nu ierakstīts blokķēdē, vai arī tiek noraidīts. Viedais līgums pilnībā garantē noteikto nosacījumu izpildi. Darījumu ģenerēšana no līguma RIDE pašlaik nav iespējama.

Mūsdienās ir divu veidu Waves viedie līgumi: viedie konti un viedie līdzekļi. Viedais konts ir parasts lietotāja konts, taču tam ir iestatīts skripts, kas kontrolē visus darījumus. Viedā konta skripts var izskatīties šādi, piemēram:

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

tx ir darījums, kas tiek apstrādāts, un mēs atļaujam izmantot modeļu saskaņošanas mehānismu tikai tad, ja tas nav pārsūtīšanas darījums. Rakstu saskaņošana programmā RIDE tiek izmantota, lai pārbaudītu darījuma veidu. Visus esošos kontus var apstrādāt viedkonta skriptā darījumu veidi.

Skripts var arī deklarēt mainīgos lielumus, izmantot “if-then-else” konstrukcijas un citas metodes, lai pilnībā pārbaudītu apstākļus. Lai nodrošinātu, ka līgumiem ir pierādāma pilnīgums un sarežģītība (izmaksas), ko ir viegli paredzēt pirms līguma izpildes sākuma, RIDE nesatur cilpas vai lēciena paziņojumus.

Citas Waves kontu funkcijas ietver “stāvokļa” klātbūtni, tas ir, konta stāvokli. Izmantojot datu darījumus (DataTransaction), konta stāvoklim varat ierakstīt bezgalīgu skaitu pāru (atslēga, vērtība). Pēc tam šo informāciju var apstrādāt gan caur REST API, gan tieši viedajā līgumā.

Katrs darījums var saturēt pierādījumu masīvu, kurā var ievadīt dalībnieka parakstu, vajadzīgā darījuma ID u.c.

Darbs ar RIDE caur IDE ļauj redzēt sastādīto līguma skatu (ja tas ir sastādīts), izveidot jaunus kontus un iestatīt tam skriptus, kā arī nosūtīt darījumus caur komandrindu.

Pilnu ciklu, ieskaitot konta izveidi, viedā līguma instalēšanu un darījumu nosūtīšanu, varat izmantot arī bibliotēku mijiedarbībai ar REST API (piemēram, C#, C, Java, JavaScript, Python, Rust, Elixir) . Lai sāktu darbu ar IDE, vienkārši noklikšķiniet uz pogas JAUNS.

Viedo līgumu izmantošanas iespējas ir plašas: no transakciju aizliegšanas uz noteiktām adresēm (“melnais saraksts”) līdz sarežģītām dApps.

Tagad apskatīsim konkrētus piemērus viedo līgumu izmantošanai uzņēmējdarbībā: rīkojot izsoles, apdrošinājot un veidojot lojalitātes programmas.

Izsoles

Viens no veiksmīgas izsoles nosacījumiem ir caurskatāmība: dalībniekiem ir jābūt pārliecinātiem, ka ar cenām nav iespējams manipulēt. To var panākt, pateicoties blokķēdei, kurā visiem dalībniekiem būs pieejami nemainīgi dati par visām likmēm un to izdarīšanas laiku.

Waves blokķēdē solījumus var reģistrēt izsoles konta stāvoklī, izmantojot DataTransaction.

Varat arī iestatīt izsoles sākuma un beigu laiku, izmantojot bloku numurus: bloku ģenerēšanas biežums Waves blokķēdē ir aptuveni vienāds ar 60 sekundes.

1. Angļu augšupejošas cenas izsole

Angļu izsoles dalībnieki piedāvā cenas, konkurējot savā starpā. Katrai jaunai likmei ir jāpārsniedz iepriekšējā. Izsole beidzas, kad vairs nav pretendentu, kas varētu pārsniegt pēdējo solījumu. Šajā gadījumā augstākajam solītājam ir jāsniedz norādītā summa.

Ir arī izsoles iespēja, kurā pārdevējs nosaka minimālo lotes cenu, un gala cenai tā ir jāpārsniedz. Pretējā gadījumā partija paliek nepārdota.

Šajā piemērā mēs strādājam ar kontu, kas īpaši izveidots izsolei. Izsoles ilgums ir 3000 bloki, un partijas sākumcena ir 0,001 VIĻŅI. Dalībnieks var likt solījumu, nosūtot DataTransaction ar atslēgu “cena” un sava piedāvājuma vērtību.

Jaunās cenas cenai ir jābūt augstākai par pašreizējo šīs atslēgas cenu, un dalībnieka kontā ir jābūt vismaz [new_bid + Commission] marķieriem. DataTransaction laukā "sūtītājs" ir jāieraksta solītāja adrese, un pašreizējam solīšanas bloka augstumam jābūt izsoles periodā.

Ja izsoles beigās dalībnieks ir noteicis augstāko cenu, viņš var nosūtīt ExchangeTransaction, lai samaksātu par atbilstošo loti par norādīto cenu un valūtu pāri.

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. Nīderlandes cenu samazināšanās izsole

Nīderlandes izsolē partija sākotnēji tiek piedāvāta par augstāku cenu, nekā pircējs ir gatavs maksāt. Cena tiek samazināta soli pa solim, līdz kāds no dalībniekiem piekrīt pirkt partiju par pašreizējo cenu.

Šajā piemērā mēs izmantojam tās pašas konstantes kā iepriekšējā, kā arī cenas soli, kad delta samazinās. Konta skripts pārbauda, ​​vai dalībnieks patiešām ir pirmais, kas veic likmi. Pretējā gadījumā blokķēde nepieņem 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. Izsole “viss maksā”

“All-pay” ir izsole, kurā visi dalībnieki maksā solījumu neatkarīgi no tā, kurš uzvar. Katrs jauns dalībnieks maksā solījumu, un dalībnieks, kurš veic maksimālo solījumu, uzvar.

Mūsu piemērā katrs izsoles dalībnieks, izmantojot DataTransaction, veic cenu ar (atslēga, vērtība)* = (“uzvarētājs”, adrese), (“cena”, cena). Šāds DataTransaction tiek apstiprināts tikai tad, ja šim dalībniekam jau ir TransferTransaction ar parakstu un viņa solījums ir augstāks par visiem iepriekšējiem. Izsole turpinās līdz ir sasniegts beigu Augstums.

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

Apdrošināšana / Kopfinansēšana

Apskatīsim situāciju, kad ir nepieciešams apdrošināt lietotāju īpašumus pret finansiāliem zaudējumiem. Piemēram, lietotājs vēlas garantiju, ka marķiera vērtības samazināšanās gadījumā viņš varēs atgūt visu par šiem žetoniem samaksāto summu un ir gatavs maksāt saprātīgu apdrošināšanas summu.

Lai to īstenotu, ir jāizsniedz “apdrošināšanas marķieri”. Pēc tam apdrošinājuma ņēmēja kontā tiek instalēts skripts, kas ļauj izpildīt tikai tos ExchangeTransactions, kas atbilst noteiktiem nosacījumiem.

Lai novērstu dubultu tēriņu, ir nepieciešams iepriekš pieprasīt lietotājam nosūtīt DataTransaction uz apdrošinājuma ņēmēja kontu ar (atslēga, vērtība) = (purchaseTransactionId, sellOrderId) un aizliegt sūtīt DataTransactions ar atslēgu, kas jau ir izmantota.

Tāpēc lietotāja apliecinājumos ir jāietver apdrošināšanas marķiera pirkuma darījuma ID. Valūtu pārim ir jābūt tādam pašam kā pirkuma darījumā. Izmaksām arī jābūt vienādām ar pirkuma brīdī noteiktajām izmaksām, no kurām atskaitīta apdrošināšanas cena.

Tiek saprasts, ka pēc tam apdrošināšanas konts iegādājas no lietotāja apdrošināšanas žetonus par cenu, kas nav zemāka par to, par kuru viņš tos iegādājās: apdrošināšanas konts izveido Exchange Darījumu, lietotājs paraksta pasūtījumu (ja darījums ir veikts pareizi), apdrošināšanas konts paraksta otro pasūtījumu un visu darījumu un nosūta to uz blokķēdi.

Ja pirkums nenotiek, lietotājs var izveidot ExchangeTransaction saskaņā ar skriptā aprakstītajiem noteikumiem un nosūtīt darījumu uz blokķēdi. Tādā veidā lietotājs var atgriezt apdrošināto žetonu iegādei iztērēto naudu.

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

Apdrošināšanas marķieri var padarīt par viedo aktīvu, piemēram, lai aizliegtu tā nodošanu trešajām personām.

Šo shēmu var ieviest arī kolektīvās finansēšanas žetoniem, kuri tiek atdoti īpašniekiem, ja nav iekasēta nepieciešamā summa.

Darījumu nodokļi

Viedie līgumi ir piemērojami arī gadījumos, kad nepieciešams iekasēt nodokli par katru darījumu ar vairāku veidu aktīviem. To var izdarīt, izmantojot jaunu līdzekli ar instalētu sponsorēšana darījumiem ar viedajiem līdzekļiem:

1. Mēs izlaižam FeeCoin, kas lietotājiem tiks nosūtīts par fiksētu cenu: 0,01 VIĻŅI = 0,001 FeeCoin.

2. Iestatiet FeeCoin sponsorēšanu un maiņas kursu: 0,001 WAVES = 0,001 FeeCoin.

3. Iestatiet viedajam īpašumam šādu skriptu:

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
}

Tagad katru reizi, kad kāds pārskaitīs N viedo īpašumu, viņš jums piešķirs FeeCoin N/taxDivisor apjomā (kuru no jums var iegādāties par 10 *N/taxDivisor WAVES), un jūs piešķirsit kalnraču N/taxDivisor WAVES. Rezultātā jūsu peļņa (nodoklis) būs 9*N / taxDivisor WAVES.

Varat arī veikt nodokļu uzlikšanu, izmantojot viedo līdzekļu skriptu un 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
}

Naudas atmaksa un lojalitātes programmas

Cashback ir lojalitātes programmas veids, kurā pircējs atgūst daļu no summas, kas iztērēta par produktu vai pakalpojumu.

Īstenojot šo gadījumu, izmantojot viedo kontu, mums ir jāpārbauda pierādījumi tādā pašā veidā, kā mēs to darījām apdrošināšanas gadījumā. Lai novērstu dubultu tēriņu, lietotājam pirms naudas atmaksas saņemšanas ir jānosūta DataTransaction ar (atslēga, vērtība) = (purchaseTransactionId, cashbackTransactionId).

Mums ir arī jāiestata aizliegums esošajām atslēgām, izmantojot DataTransaction. cashbackDivisor — vienība dalīta ar naudas atmaksas daļu. Tie. ja naudas atmaksas daļa ir 0.1, tad naudas atmaksas dalītājs 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)
}

Atomu maiņa

Atomic swap ļauj lietotājiem apmainīties ar aktīviem bez apmaiņas palīdzības. Izmantojot atomu mijmaiņas darījumu, abiem darījuma dalībniekiem tas ir jāapstiprina noteiktā laika periodā.

Ja vismaz viens no dalībniekiem nesniedz pareizu darījuma apstiprinājumu darījumam atvēlētajā laikā, darījums tiek atcelts un apmaiņa nenotiek.

Mūsu piemērā mēs izmantosim šādu viedkonta skriptu:

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ākamajā rakstā apskatīsim viedo kontu izmantošanu tādos finanšu instrumentos kā opcijas, fjūčeri un rēķini.

Avots: www.habr.com

Pievieno komentāru