Anwendung von Waves Smart Accounts: von Auktionen bis hin zu Bonusprogrammen

Anwendung von Waves Smart Accounts: von Auktionen bis hin zu Bonusprogrammen

Blockchain wird oft nur mit Kryptowährungen in Verbindung gebracht, der Anwendungsbereich der DLT-Technologie ist jedoch viel umfassender. Einer der vielversprechendsten Bereiche für den Einsatz von Blockchain ist ein intelligenter Vertrag, der automatisch ausgeführt wird und kein Vertrauen zwischen den Parteien erfordert, die ihn abgeschlossen haben.

RIDE ist eine Sprache für Smart Contracts

Waves hat eine spezielle Sprache für Smart Contracts entwickelt – RIDE. Die vollständige Dokumentation ist hier. Und hier - Artikel zu diesem Thema auf Habr.

Der RIDE-Vertrag ist ein Prädikat und gibt als Ausgabe „wahr“ oder „falsch“ zurück. Dementsprechend wird die Transaktion entweder in der Blockchain aufgezeichnet oder abgelehnt. Ein Smart Contract gewährleistet in vollem Umfang die Erfüllung der festgelegten Bedingungen. Es ist derzeit nicht möglich, in RIDE Transaktionen aus einem Vertrag zu generieren.

Derzeit gibt es zwei Arten von Waves-Smart Contracts: Smart Accounts und Smart Assets. Ein Smart Account ist ein reguläres Benutzerkonto, für das jedoch ein Skript festgelegt ist, das alle Transaktionen steuert. Das Smart-Account-Skript könnte so aussehen:

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

tx ist eine Verarbeitungstransaktion, die wir mithilfe des Mustervergleichsmechanismus nur dann auflösen, wenn es sich nicht um eine Übertragungstransaktion handelt. Der Mustervergleich in RIDE wird verwendet, um die Art einer Transaktion zu überprüfen. Im Smart-Account-Skript sind alle vorhanden Transaktionsarten.

Im Skript können auch Variablen deklariert werden, „if-then-else“-Konstruktionen und andere Methoden zur vollständigen Überprüfung von Bedingungen verwendet werden. Damit Verträge eine nachweisbare Beendigung und eine Komplexität (Kosten) aufweisen, die vor der Vertragsausführung leicht vorhersehbar ist, enthält RIDE keine Schleifen und Sprunganweisungen.

Zu den weiteren Merkmalen von Waves-Konten gehört das Vorhandensein eines „Status“, also des Status des Kontos. Über Datentransaktionen (DataTransaction) können unendlich viele Paare (Schlüssel, Wert) in den Kontostand geschrieben werden. Darüber hinaus können diese Informationen sowohl über die REST-API als auch direkt im Smart Contract verarbeitet werden.

Jede Transaktion kann eine Reihe von Nachweisen enthalten, in die Sie die Unterschrift des Teilnehmers, die ID der erforderlichen Transaktion usw. eingeben können.

Arbeiten mit RIDE via IDE ermöglicht es Ihnen, die kompilierte Ansicht des Vertrags anzuzeigen (sofern er kompiliert ist), neue Konten zu erstellen und Skripts dafür festzulegen sowie Transaktionen über die Befehlszeile zu senden.

Für einen vollständigen Zyklus, einschließlich der Erstellung eines Kontos, der Installation eines Smart Contracts darauf und dem Senden von Transaktionen, können Sie die Bibliothek auch für die Interaktion mit der REST-API verwenden (z. B. C#, C, Java, JavaScript, Python, Rust, Elixir). . Um mit der IDE zu arbeiten, klicken Sie einfach auf die Schaltfläche NEU.

Die Einsatzmöglichkeiten von Smart Contracts sind vielfältig: vom Verbot von Transaktionen an bestimmte Adressen („Schwarze Liste“) bis hin zu komplexen dApps.

Schauen wir uns nun konkrete Beispiele für den Einsatz intelligenter Verträge in Unternehmen an: bei der Durchführung von Auktionen, bei Versicherungen und bei der Erstellung von Treueprogrammen.

Auktionen

Eine der Voraussetzungen für eine erfolgreiche Auktion ist Transparenz: Die Teilnehmer müssen sicher sein, dass Gebote nicht manipuliert werden können. Dies kann dank der Blockchain erreicht werden, bei der allen Teilnehmern unveränderliche Daten über alle Wetten und den Zeitpunkt ihrer Durchführung zur Verfügung stehen.

Auf der Waves-Blockchain können Gebote über eine DataTransaction auf den Stand des Auktionskontos geschrieben werden.

Sie können die Start- und Endzeit der Auktion auch mithilfe von Blocknummern festlegen: Die Häufigkeit der Blockgenerierung in der Waves-Blockchain ist ungefähr gleich 60 Sekunden.

1. Englische Auktion mit steigendem Preis

Die Teilnehmer der englischen Auktion geben Gebote ab und konkurrieren miteinander. Jeder neue Satz muss den vorherigen übersteigen. Die Auktion endet, wenn niemand mehr bereit ist, das letzte Gebot zu übertreffen. In diesem Fall muss der Teilnehmer, der das höchste Gebot abgegeben hat, den angegebenen Betrag erbringen.

Es gibt auch eine Auktionsoption, bei der der Verkäufer einen Mindestpreis für das Los festlegt und der Endpreis diesen überschreiten muss. Ansonsten bleibt das Los unverkauft.

In diesem Beispiel arbeiten wir mit einem Konto, das speziell für die Durchführung einer Auktion erstellt wurde. Die Dauer der Auktion beträgt 3000 Blöcke und der Anfangspreis des Loses beträgt 0,001 WAVES. Ein Teilnehmer kann ein Gebot abgeben, indem er eine DataTransaction mit dem Schlüssel „Preis“ und dem Wert seines Gebots sendet.

Der Preis des neuen Gebots muss höher sein als der aktuelle Preis für diesen Schlüssel und der Teilnehmer muss mindestens [neues_Gebot + Provision] Token auf dem Konto haben. In der DataTransaction muss im Feld „Absender“ die Adresse des Teilnehmers eingetragen sein und die aktuelle Höhe des Gebotsblocks muss innerhalb der Grenzen des Auktionszeitraums liegen.

Wenn der Teilnehmer am Ende der Auktion den höchsten Preis festgelegt hat, kann er eine ExchangeTransaction senden, um das entsprechende Los zum angegebenen Preis und Währungspaar zu bezahlen.

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. Niederländische Auktion mit sinkendem Preis

Bei einer niederländischen Auktion wird ein Los zunächst zu einem höheren Preis angeboten, als der Käufer zu zahlen bereit ist. Der Preis wird schrittweise reduziert, bis einer der Teilnehmer zustimmt, das Los zum aktuellen Preis zu kaufen.

In diesem Beispiel verwenden wir dieselben Konstanten wie im vorherigen sowie den Preisschritt, wenn das Delta fällt. Das Kontoskript prüft, ob der Teilnehmer tatsächlich der erste ist, der eine Wette platziert. Andernfalls wird die DataTransaction von der Blockchain nicht akzeptiert.

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. All-Pay-Auktion

„All-Pay“ – eine Auktion, bei der alle Teilnehmer das Gebot zahlen, unabhängig davon, wer den Zuschlag erhält. Jeder neue Teilnehmer zahlt das Gebot und der Teilnehmer, der das Höchstgebot abgegeben hat, gewinnt das Los.

In unserem Beispiel bietet jeder Bieter über eine DataTransaction mit (key, value)* = ("Gewinner", Adresse),("Preis", Preis). Eine solche Datentransaktion wird nur dann genehmigt, wenn für diesen Teilnehmer mit seiner Unterschrift bereits eine Transfertransaktion vorliegt und sein Tarif höher ist als alle vorherigen. Die Auktion wird fortgesetzt, bis endHeight erreicht ist.

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

Versicherungen / Crowdfunding

Stellen Sie sich eine Situation vor, in der Sie das Vermögen der Benutzer gegen finanzielle Verluste versichern müssen. Beispielsweise möchte ein Benutzer die Garantie haben, dass er im Falle einer Wertminderung des Tokens den gesamten für diese Token gezahlten Betrag zurückerstatten kann, und ist bereit, eine angemessene Versicherungssumme zu zahlen.

Um dies umzusetzen, müssen Sie „Versicherungstoken“ ausgeben. Anschließend wird auf dem Konto des Versicherungsnehmers ein Skript installiert, das nur die Ausführung von ExchangeTransactions zulässt, die bestimmte Bedingungen erfüllen.

Um doppelte Ausgaben zu vermeiden, müssen Sie den Benutzer vorab auffordern, eine DataTransaction an das Konto des Versicherungsnehmers zu senden, mit (key, value) = (purchaseTransactionId, saleOrderId) und das Senden von DataTransactions mit einem bereits verwendeten Schlüssel verbieten.

Daher müssen Benutzernachweise die Transaktions-ID des Versicherungs-Token-Kaufs enthalten. Das Währungspaar muss mit dem bei der Kauftransaktion identisch sein. Die Kosten müssen außerdem den zum Zeitpunkt des Kaufs festgelegten Kosten abzüglich des Versicherungspreises entsprechen.

Es versteht sich, dass das Versicherungskonto anschließend Versicherungstokens vom Benutzer zu einem Preis einlöst, der nicht niedriger ist als der, zu dem er sie gekauft hat: Das Versicherungskonto erstellt eine ExchangeTransaction, der Benutzer unterzeichnet die Bestellung (wenn die Transaktion korrekt durchgeführt wurde), die Das Versicherungskonto signiert die zweite Bestellung und die gesamte Transaktion und sendet sie an die Blockchain.

Kommt es zu keinem Kauf, kann der Nutzer gemäß den im Skript beschriebenen Regeln eine ExchangeTransaction erstellen und die Transaktion an die Blockchain senden. So kann der Nutzer das für den Kauf versicherter Token ausgegebene Geld zurückerstatten.

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

Ein Versicherungs-Token kann beispielsweise zu einem Smart Asset gemacht werden, um dessen Weitergabe an Dritte zu verhindern.

Dieses Schema lässt sich auch für Crowdfunding-Token umsetzen, die an die Eigentümer zurückgegeben werden, wenn der erforderliche Betrag nicht eingezogen wurde.

Transaktionssteuern

Intelligente Verträge sind auch in Fällen anwendbar, in denen es erforderlich ist, Steuern für jede Transaktion mit mehreren Arten von Vermögenswerten zu erheben. Dies kann durch die Installation eines neuen Assets erfolgen Sponsoring für Transaktionen mit Smart Assets:

1. Geben Sie FeeCoin aus, das zu einem festen Preis an Benutzer gesendet wird: 0,01 WAVES = 0,001 FeeCoin.

2. Sponsoring für FeeCoin und Wechselkurs festlegen: 0,001 WAVES = 0,001 FeeCoin.

3. Legen Sie das folgende Skript für das Smart Asset fest:

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
}

Jedes Mal, wenn jemand N Smart Assets überträgt, gibt er Ihnen einen FeeCoin in Höhe von N / taxDivisor (den Sie für 10 *N / taxDivisor WAVES bei Ihnen kaufen können), und Sie geben N / taxDivisor WAVES an den Miner. Infolgedessen beträgt Ihr Gewinn (Steuer) 9*N / taxDivisor WAVES.

Sie können die Besteuerung auch mithilfe eines Smart-Asset-Skripts und MassTransferTransaction durchführen:

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- und Treueprogramme

Cashback ist eine Art Treueprogramm, bei dem ein Teil des für ein Produkt oder eine Dienstleistung ausgegebenen Betrags an den Käufer zurückerstattet wird.

Bei der Umsetzung dieses Falles mit einem Smart Account müssen wir die Nachweise auf die gleiche Weise prüfen, wie wir es im Versicherungsfall getan haben. Um doppelte Ausgaben zu verhindern, bevor er Cashback erhält, muss der Benutzer eine DataTransaction mit (key, value) = (purchaseTransactionId, cashbackTransactionId) senden.

Wir müssen auch ein Verbot für bereits vorhandene Schlüssel mithilfe von DataTransaction einrichten. CashbackDivisor – eine Einheit dividiert durch den Cashback-Anteil. Diese. wenn der Cashback-Anteil 0.1 beträgt, dann ist 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)
}

Atomtausch

Mit Atomic Swap können Benutzer Vermögenswerte ohne die Hilfe einer Börse austauschen. Bei einem Atomic Swap müssen beide Parteien der Transaktion diese innerhalb einer bestimmten Frist bestätigen.

Wenn mindestens einer der Teilnehmer innerhalb der für die Transaktion vorgesehenen Zeit keine korrekte Bestätigung der Transaktion vorlegt, wird die Transaktion storniert und der Umtausch findet nicht statt.

In unserem Beispiel verwenden wir das folgende Smart-Account-Skript:

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
}

Im nächsten Artikel werden wir uns mit der Verwendung von Smart Accounts in Finanzinstrumenten wie Optionen, Futures und Wechseln befassen.

Source: habr.com

Kommentar hinzufügen