Toepassingen van Waves smart accounts: van veilingen tot bonusprogramma’s

Toepassingen van Waves smart accounts: van veilingen tot bonusprogramma’s

Blockchain wordt vaak alleen geassocieerd met cryptocurrencies, maar de toepassingsgebieden van DLT-technologie zijn veel breder. Een van de meest veelbelovende gebieden voor het gebruik van blockchain is een slim contract dat automatisch wordt uitgevoerd en geen vertrouwen vereist tussen de partijen die het zijn aangegaan.

RIDE – een taal voor slimme contracten

Waves heeft een speciale taal voor slimme contracten ontwikkeld: RIDE. De volledige documentatie bevindt zich hier. En hier - artikel over dit onderwerp op Habr.

Het RIDE-contract is een predikaat en retourneert 'waar' of 'onwaar' als uitvoer. Dienovereenkomstig wordt de transactie vastgelegd in de blockchain of afgewezen. Het slimme contract garandeert volledig de vervulling van gespecificeerde voorwaarden. Het genereren van transacties vanuit een contract in RIDE is momenteel niet mogelijk.

Tegenwoordig zijn er twee soorten slimme contracten van Waves: slimme accounts en slimme activa. Een smart account is een gewoon gebruikersaccount, maar er is een script voor ingesteld dat alle transacties beheert. Een slim accountscript kan er bijvoorbeeld zo uitzien:

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

tx is een transactie die wordt verwerkt en die we alleen toestaan ​​om het patroonmatchingsmechanisme te gebruiken als het geen overdrachtstransactie is. Patroonmatching in RIDE wordt gebruikt om het type transactie te controleren. Alle bestaande accounts kunnen worden verwerkt in het smart account-script transactie soorten.

Het script kan ook variabelen declareren, ‘if-then-else’-constructies en andere methoden gebruiken om de voorwaarden volledig te controleren. Om ervoor te zorgen dat contracten een aantoonbare volledigheid en complexiteit (kosten) hebben die gemakkelijk te voorspellen zijn voordat de contractuitvoering begint, bevat RIDE geen loops of jump-instructies.

Andere kenmerken van Waves-accounts zijn onder meer de aanwezigheid van een ‘status’, dat wil zeggen de status van het account. U kunt een oneindig aantal paren (sleutel, waarde) naar de accountstatus schrijven met behulp van datatransacties (DataTransaction). Deze informatie kan vervolgens zowel via de REST API als rechtstreeks in het slimme contract worden verwerkt.

Elke transactie kan een reeks bewijzen bevatten, waarin de handtekening van de deelnemer, het ID van de vereiste transactie, enz. kan worden ingevoerd.

Werken met RIDE via IDE Hiermee kunt u de samengestelde weergave van het contract bekijken (als het is samengesteld), nieuwe accounts aanmaken en er scripts voor instellen, en transacties verzenden via de opdrachtregel.

Voor een volledige cyclus, inclusief het aanmaken van een account, het installeren van een slim contract erop en het verzenden van transacties, kunt u ook een bibliotheek gebruiken voor interactie met de REST API (bijvoorbeeld C#, C, Java, JavaScript, Python, Rust, Elixir) . Om met de IDE te gaan werken, klikt u gewoon op de knop NIEUW.

De mogelijkheden voor het gebruik van slimme contracten zijn breed: van het verbieden van transacties tot bepaalde adressen (“zwarte lijst”) tot complexe dApps.

Laten we nu eens kijken naar specifieke voorbeelden van het gebruik van slimme contracten in het bedrijfsleven: bij het houden van veilingen, verzekeringen en het opzetten van loyaliteitsprogramma’s.

Veilingen

Eén van de voorwaarden voor een succesvolle veiling is transparantie: deelnemers moeten erop kunnen vertrouwen dat biedingen niet kunnen worden gemanipuleerd. Dit kan worden bereikt dankzij de blockchain, waar onveranderlijke gegevens over alle weddenschappen en het tijdstip waarop ze zijn gedaan voor alle deelnemers beschikbaar zullen zijn.

Op de Waves-blockchain kunnen via DataTransaction biedingen worden vastgelegd in de veilingaccountstatus.

Je kunt ook de start- en eindtijd van de veiling instellen met behulp van bloknummers: de frequentie van blokgeneratie in de Waves-blockchain is ongeveer gelijk aan 60 seconden.

1. Engelse oplopende prijsveiling

Deelnemers aan een Engelse veiling plaatsen biedingen in competitie met elkaar. Elke nieuwe weddenschap moet de vorige overschrijden. De veiling eindigt zodra er geen bieders meer zijn die het laatste bod overschrijden. In dit geval moet de hoogste bieder het aangegeven bedrag verstrekken.

Er is ook een veilingoptie waarbij de verkoper een minimumprijs voor het kavel vaststelt, en de uiteindelijke prijs moet hoger zijn. Anders blijft het lot onverkocht.

In dit voorbeeld werken we met een account dat speciaal voor de veiling is aangemaakt. De veilingduur is 3000 blokken en de startprijs van het kavel is 0,001 WAVES. Een deelnemer kan een bod plaatsen door een DataTransactie te versturen met als sleutel “prijs” en de waarde van zijn bod.

De prijs van het nieuwe bod moet hoger zijn dan de huidige prijs voor deze sleutel, en de deelnemer moet minimaal [new_bid + commissie] tokens in zijn account hebben. Het adres van de bieder moet worden vastgelegd in het veld "afzender" in de DataTransaction, en de huidige biedblokhoogte moet binnen de veilingperiode vallen.

Als de deelnemer aan het einde van de veiling de hoogste prijs heeft ingesteld, kan hij een Ruiltransactie sturen om het overeenkomstige lot tegen de opgegeven prijs en valutapaar te betalen.

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. Nederlandse veiling van dalende prijzen

Op een Nederlandse veiling wordt een kavel in eerste instantie aangeboden tegen een hogere prijs dan wat de koper bereid is te betalen. De prijs wordt stap voor stap verlaagd totdat een van de deelnemers ermee instemt het kavel tegen de huidige prijs te kopen.

In dit voorbeeld gebruiken we dezelfde constanten als in het vorige, evenals de prijsstap wanneer de delta afneemt. Het accountscript controleert of de deelnemer inderdaad de eerste is die een weddenschap plaatst. Anders wordt de DataTransactie niet geaccepteerd door de blockchain.

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. Veiling “all-pay”

“All-pay” is een veiling waarbij alle deelnemers het bod betalen, ongeacht wie het kavel wint. Elke nieuwe deelnemer betaalt een bod, en de deelnemer die het maximale bod doet, wint het lot.

In ons voorbeeld plaatst elke veilingdeelnemer een bod via DataTransaction met (sleutel, waarde)* = (“winnaar”, adres),(“prijs”, prijs). Een dergelijke DataTransactie wordt alleen goedgekeurd als deze deelnemer al een OverdrachtTransactie met handtekening heeft en zijn bod hoger is dan alle voorgaande. De veiling gaat door totdat endHeight is bereikt.

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

Verzekeringen / Crowdfunding

Laten we eens een situatie bekijken waarin u de activa van gebruikers moet verzekeren tegen financiële verliezen. Een gebruiker wil bijvoorbeeld de garantie dat als een token in waarde daalt, hij het volledige bedrag dat voor deze tokens is betaald, kan terugkrijgen en bereid is een redelijk bedrag aan verzekering te betalen.

Om dit te implementeren moeten “verzekeringstokens” worden uitgegeven. Vervolgens wordt er een script op de rekening van de verzekeringnemer geïnstalleerd, waardoor alleen die Wisseltransacties die aan bepaalde voorwaarden voldoen, kunnen worden uitgevoerd.

Om dubbele uitgaven te voorkomen, moet u de gebruiker verzoeken om vooraf een DataTransaction naar de account van de verzekeringnemer te sturen met (key, value) = (purchaseTransactionId, sellOrderId) en het verzenden van DataTransactions met een sleutel die al is gebruikt, verbieden.

Daarom moeten de bewijzen van de gebruiker de transactie-ID van de aankoop van het verzekeringstoken bevatten. Het valutapaar moet hetzelfde zijn als in de aankooptransactie. De kosten moeten eveneens gelijk zijn aan de kosten die bij de aankoop zijn vastgesteld, minus de verzekeringsprijs.

Het is duidelijk dat de verzekeringsrekening vervolgens verzekeringstokens van de gebruiker koopt tegen een prijs die niet lager is dan de prijs waarvoor hij ze heeft gekocht: de verzekeringsrekening creëert een Uitwisselingstransactie, de gebruiker ondertekent de bestelling (als de transactie correct is voltooid), de verzekeringsrekening ondertekent de tweede bestelling en de gehele transactie en stuurt deze naar de blockchain.

Als er geen aankoop plaatsvindt, kan de gebruiker een ExchangeTransaction aanmaken volgens de regels die in het script zijn beschreven en de transactie naar de blockchain sturen. Op deze manier kan de gebruiker het geld dat is uitgegeven aan de aankoop van verzekerde tokens teruggeven.

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

Van een verzekeringstoken kan een slim bezit worden gemaakt, bijvoorbeeld om de overdracht ervan aan derden te verbieden.

Deze regeling kan ook worden geïmplementeerd voor crowdfunding-tokens, die worden teruggegeven aan de eigenaren als het vereiste bedrag niet is verzameld.

Transactiebelastingen

Slimme contracten zijn ook van toepassing in gevallen waarin het nodig is om belasting te innen op elke transactie met verschillende soorten activa. Dit kan gedaan worden door een nieuwe asset te laten installeren sponsoring voor transacties met slimme activa:

1. Wij geven FeeCoin uit, die tegen een vaste prijs naar gebruikers wordt verzonden: 0,01 WAVES = 0,001 FeeCoin.

2. Stel de sponsoring voor FeeCoin en de wisselkoers in: 0,001 WAVES = 0,001 FeeCoin.

3. Stel het volgende script in voor het slimme asset:

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
}

Elke keer dat iemand N smart assets overdraagt, krijgt u FeeCoin ter waarde van N/taxDivisor (die bij u kan worden gekocht voor 10 *N/taxDivisor WAVES), en u geeft de mijnwerker N/taxDivisor WAVES. Als gevolg hiervan zal uw winst (belasting) 9*N / taxDivisor WAVES zijn.

U kunt ook belastingheffing uitvoeren met behulp van een slim activascript en 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- en loyaliteitsprogramma's

Cashback is een vorm van loyaliteitsprogramma waarbij de koper een deel van het bedrag dat hij aan een product of dienst besteedt, terugkrijgt.

Bij de uitvoering van deze zaak met behulp van een smart account moeten we de bewijzen op dezelfde manier controleren als bij de verzekeringszaak. Om dubbele uitgaven te voorkomen, moet de gebruiker een DataTransaction verzenden met (key, value) = (purchaseTransactionId, cashbackTransactionId) voordat hij cashback ontvangt.

We moeten ook een verbod instellen op bestaande sleutels die DataTransaction gebruiken. cashbackDivisor - eenheid gedeeld door het cashbackaandeel. Die. als het cashback-aandeel 0.1 is, dan 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)
}

Atomaire ruil

Met Atomic Swap kunnen gebruikers activa uitwisselen zonder de hulp van een beurs. Bij een atomic swap zijn beide deelnemers aan de transactie verplicht om deze binnen een bepaalde periode te bevestigen.

Als ten minste één van de deelnemers de transactie niet correct bevestigt binnen de voor de transactie toegewezen tijd, wordt de transactie geannuleerd en vindt de uitwisseling niet plaats.

In ons voorbeeld gebruiken we het volgende slimme accountscript:

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
}

In het volgende artikel zullen we kijken naar het gebruik van slimme accounts in financiële instrumenten zoals opties, futures en rekeningen.

Bron: www.habr.com

Voeg een reactie