Aplikoj de Waves inteligentaj kontoj: de aŭkcioj ĝis bonusaj programoj

Aplikoj de Waves inteligentaj kontoj: de aŭkcioj ĝis bonusaj programoj

Blokoĉeno ofte estas asociita nur kun kriptaj moneroj, sed la areoj de apliko de DLT-teknologio estas multe pli larĝaj. Unu el la plej promesplenaj areoj por la uzo de blokĉeno estas inteligenta kontrakto, kiu efektiviĝas aŭtomate kaj ne postulas fidon inter la partioj, kiuj eniris ĝin.

RIDE - lingvo por inteligentaj kontraktoj

Waves evoluigis specialan lingvon por inteligentaj kontraktoj - RIDE. Ĝia kompleta dokumentaro troviĝas tie. Kaj ĉi tie - artikolo pri ĉi tiu temo sur Habr.

La RIDE-kontrakto estas predikativo kaj resendas "vera" aŭ "malvera" kiel eligo. Sekve, la transakcio estas aŭ registrita en la blokĉeno aŭ malakceptita. La inteligenta kontrakto plene garantias la plenumon de specifitaj kondiĉoj. Generi transakciojn de kontrakto en RIDE nuntempe ne eblas.

Hodiaŭ ekzistas du specoj de inteligentaj kontraktoj de Waves: inteligentaj kontoj kaj inteligentaj aktivoj. Saĝa konto estas kutima uzantkonto, sed skripto estas starigita por ĝi, kiu kontrolas ĉiujn transakciojn. Saĝa konta skripto povus aspekti jene, ekzemple:

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

tx estas transakcio prilaborata, kiun ni permesas uzi la ŝablonon-mekanismon nur se ĝi ne estas transiga transakcio. Ŝablona kongruo en RIDE estas uzata por kontroli la tipon de transakcio. Ĉiuj ekzistantaj kontoj povas esti prilaboritaj en la inteligenta konta skripto tipoj de transakcio.

La skripto ankaŭ povas deklari variablojn, uzi "se-tiam-alie" konstrukciojn kaj aliajn metodojn por plene kontroli kondiĉojn. Por certigi ke kontraktoj havas pruveblan kompletecon kaj kompleksecon (kosto), kiu estas facile antaŭdiri antaŭ ol kontrakt-ekzekuto komenciĝas, RIDE ne enhavas buklojn aŭ saltdeklarojn.

Aliaj trajtoj de Waves-kontoj inkluzivas la ĉeeston de "ŝtato", tio estas, la stato de la konto. Vi povas skribi senfinan nombron da paroj (ŝlosilo, valoro) al la konta stato uzante datumtransakciojn (DataTransaction). Ĉi tiuj informoj tiam povas esti prilaboritaj ambaŭ per la REST API kaj rekte en la inteligenta kontrakto.

Ĉiu transakcio povas enhavi aron da pruvoj, en kiuj la subskribo de la partoprenanto, la ID de la postulata transakcio, ktp.

Laborante kun RIDE per TIE permesas al vi vidi la kompilitan vidon de la kontrakto (se ĝi estas kompilita), krei novajn kontojn kaj agordi skriptojn por ĝi, kaj sendi transakciojn per la komandlinio.

Por plena ciklo, inkluzive de kreado de konto, instalo de inteligenta kontrakto sur ĝi kaj sendado de transakcioj, vi ankaŭ povas uzi bibliotekon por interagi kun la REST API (ekzemple, C#, C, Java, JavaScript, Python, Rust, Elixir) . Por komenci labori kun la IDE, simple alklaku la NOVA butonon.

La eblecoj por uzi inteligentajn kontraktojn estas larĝaj: de malpermesado de transakcioj al certaj adresoj ("nigra listo") ĝis kompleksaj dApps.

Nun ni rigardu specifajn ekzemplojn de la uzo de inteligentaj kontraktoj en komerco: dum aŭkcioj, asekuro kaj kreado de lojalecaj programoj.

Aŭkcioj

Unu el la kondiĉoj por sukcesa aŭkcio estas travidebleco: partoprenantoj devas esti certaj, ke estas neeble manipuli ofertojn. Ĉi tio povas esti atingita danke al la blokĉeno, kie neŝanĝeblaj datumoj pri ĉiuj vetoj kaj la tempo kiam ili estis faritaj estos disponeblaj por ĉiuj partoprenantoj.

Sur la Waves-blokoĉeno, ofertoj povas esti registritaj en la aŭkcia konto-ŝtato per DataTransaction.

Vi ankaŭ povas agordi la komencon kaj fintempon de la aŭkcio per blokaj nombroj: la ofteco de blokgenerado en la blokĉeno Waves estas proksimume egala al 60 sekundoj.

1. Angla ascenda prezo aŭkcio

Partoprenantoj en angla aŭkcio faras ofertojn en konkurado unu kun la alia. Ĉiu nova veto devas superi la antaŭan. La aŭkcio finiĝas kiam ne plu estas proponantoj por superi la lastan oferton. En ĉi tiu kazo, la plej alta proponanto devas provizi la deklaritan sumon.

Ankaŭ ekzistas aŭkcia opcio, en kiu la vendisto fiksas minimuman prezon por la loto, kaj la fina prezo devas superi ĝin. Alie, la loto restas nevendita.

En ĉi tiu ekzemplo, ni laboras kun konto specife kreita por la aŭkcio. La aŭkcia daŭro estas 3000 blokoj, kaj la komenca prezo de la loto estas 0,001 ONDAS. Partoprenanto povas fari oferton sendante DataTransaction kun la ŝlosilo "prezo" kaj la valoro de sia oferto.

La prezo de la nova oferto devas esti pli alta ol la nuna prezo por ĉi tiu ŝlosilo, kaj la partoprenanto devas havi almenaŭ [new_bid + komisiono] ĵetonojn en sia konto. La adreso de la proponanto devas esti registrita en la kampo "sendinto" en la DataTransaction, kaj la nuna ofertbloko alteco devas esti ene de la aŭkcia periodo.

Se ĉe la fino de la aŭkcio la partoprenanto fiksis la plej altan prezon, li povas sendi Interŝanĝan Transakcion por pagi la respondan loton je la specifita prezo kaj valutoparo.

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. Nederlanda aŭkcio de malkreskantaj prezoj

En nederlanda aŭkcio, multo estas komence ofertita je prezo pli alta ol tio, kion la aĉetanto pretas pagi. La prezo malpliiĝas paŝon post paŝo ĝis unu el la partoprenantoj konsentas aĉeti la loton je la nuna prezo.

En ĉi tiu ekzemplo ni uzas la samajn konstantojn kiel en la antaŭa, same kiel la prezpaŝon kiam delto malpliiĝas. La konta skripto kontrolas ĉu la partoprenanto ja estas la unua, kiu vetas. Alie, la DataTransaction ne estas akceptita de la blokĉeno.

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. Aŭkcio "tute pagi"

"Ĉiu-pago" estas aŭkcio en kiu ĉiuj partoprenantoj pagas la oferton, sendepende de kiu gajnas la loton. Ĉiu nova partoprenanto pagas oferton, kaj la partoprenanto kiu faras la maksimuman oferton gajnas la loton.

En nia ekzemplo, ĉiu aŭkcia partoprenanto metas oferton per DataTransaction kun (ŝlosilo, valoro)* = ("gajninto", adreso), ("prezo", prezo). Tia DataTransaction estas aprobita nur se ĉi tiu partoprenanto jam havas TransferTransaction kun sia subskribo kaj lia oferto estas pli alta ol ĉiuj antaŭaj. La aŭkcio daŭras ĝis finAlteco estas atingita.

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

Asekuro / Crowdfunding

Ni konsideru situacion, kie vi devas certigi la aktivojn de uzantoj kontraŭ financaj perdoj. Ekzemple, uzanto volas garantion, ke se ĵetono malplivaloriĝas, li povos reakiri la plenan kvanton pagita por ĉi tiuj ĵetonoj, kaj pretas pagi akcepteblan kvanton da asekuro.

Por efektivigi ĉi tion, "asekuraj ĵetonoj" devas esti eldonitaj. Tiam skripto estas instalita sur la konto de la posedanto, permesante nur tiujn Interŝanĝojn, kiuj plenumas iujn kondiĉojn, esti efektivigitaj.

Por malhelpi duoblan elspezadon, vi devas peti la uzanton sendi DataTransaction al la konto de la polisulo anticipe kun (ŝlosilo, valoro) = (purchaseTransactionId, sellOrderId) kaj malpermesi sendi DataTransactions kun ŝlosilo kiu jam estis uzita.

Tial, la pruvoj de la uzanto devas enhavi la transakcian ID de la asekura ĵetono. La valuta paro devas esti la sama kiel en la aĉeta transakcio. La kosto ankaŭ devas esti egala al tiu fiksita en la momento de la aĉeto, minus la prezon de asekuro.

Estas komprenite, ke poste la asekura konto aĉetas asekurajn ĵetonojn de la uzanto je prezo ne pli malalta ol tiu, ĉe kiu li aĉetis ilin: la asekura konto kreas Interŝanĝan Transakcion, la uzanto subskribas la mendon (se la transakcio estas plenumita ĝuste), la asekura konto subskribas la duan ordon kaj la tutan transakcion kaj sendas ĝin al la blokĉeno.

Se neniu aĉeto okazas, la uzanto povas krei ExchangeTransaction laŭ la reguloj priskribitaj en la skripto kaj sendi la transakcion al la blokĉeno. Tiel la uzanto povas redoni la monon elspezitan por la aĉeto de asekuritaj ĵetonoj.

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

Asekura ĵetono povas fariĝi inteligenta valoraĵo, ekzemple, por malpermesi ĝian translokigon al triaj partioj.

Ĉi tiu skemo ankaŭ povas esti efektivigita por crowdfunding-ĵetonoj, kiuj estas resenditaj al la posedantoj se la postulata kvanto ne estis kolektita.

Transakciaj impostoj

Inteligentaj kontraktoj ankaŭ aplikeblas en kazoj kie necesas kolekti imposton sur ĉiu transakcio kun pluraj specoj de aktivoj. Ĉi tio povas esti farita per nova valoraĵo kun instalita sponsorado por transakcioj kun inteligentaj aktivoj:

1. Ni elsendas FeeCoin, kiu estos sendita al uzantoj je fiksa prezo: 0,01 WAVES = 0,001 FeeCoin.

2. Agordu sponsoradon por FeeCoin kaj kurzo: 0,001 WAVES = 0,001 FeeCoin.

3. Agordu la sekvan skripton por la inteligenta valoraĵo:

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
}

Nun ĉiufoje kiam iu transdonas N inteligentajn aktivojn, ili donos al vi FeeCoin en la kvanto de N/taxDivisor (kiu povas esti aĉetita de vi ĉe 10 *N/taxDivisor WAVES), kaj vi donos al la ministo N/taxDivisor WAVES. Kiel rezulto, via profito (imposto) estos 9*N / impostoDivisor WAVES.

Vi ankaŭ povas plenumi impostadon per inteligenta valoraĵo kaj 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 kaj lojalecprogramoj

Cashback estas speco de lojaleca programo en kiu la aĉetanto reakiras parton de la sumo elspezita por produkto aŭ servo.

Kiam oni efektivigas ĉi tiun kazon uzante inteligentan konton, ni devas kontroli la pruvojn same kiel ni faris en la asekura kazo. Por malhelpi duoblan elspezadon, la uzanto devas sendi DataTransaction kun (ŝlosilo, valoro) = (purchaseTransactionId, cashbackTransactionId) antaŭ ol ricevi monon.

Ni ankaŭ devas agordi malpermeson de ekzistantaj ŝlosiloj uzante DataTransaction. cashbackDivisor - unuo dividita per la kashback-akcio. Tiuj. se la kashbackparto estas 0.1, tiam 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)
}

Atoma interŝanĝo

Atoma interŝanĝo permesas al uzantoj interŝanĝi aktivaĵojn sen la helpo de interŝanĝo. Kun atoma interŝanĝo, ambaŭ partoprenantoj en la transakcio devas konfirmi ĝin en certa tempodaŭro.

Se almenaŭ unu el la partoprenantoj ne provizas ĝustan konfirmon de la transakcio en la tempo asignita por la transakcio, la transakcio estas nuligita kaj la interŝanĝo ne okazas.

En nia ekzemplo, ni uzos la jenan inteligentan kontan skripton:

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
}

En la sekva artikolo ni rigardos la uzon de inteligentaj kontoj en financaj instrumentoj kiel opcioj, estontecoj kaj biletoj.

fonto: www.habr.com

Aldoni komenton