Anvendelse af Waves smart-konti: fra auktioner til bonusprogrammer

Anvendelse af Waves smart-konti: fra auktioner til bonusprogrammer

Blockchain forbindes ofte kun med kryptovalutaer, men anvendelsesområderne for DLT-teknologi er meget bredere. Et af de mest lovende områder for brugen af ​​blockchain er en smart kontrakt, der udføres automatisk og ikke kræver tillid mellem de parter, der har indgået den.

RIDE – et sprog til smarte kontrakter

Waves har udviklet et særligt sprog til smarte kontrakter – RIDE. Dens fuldstændige dokumentation findes her. Og her - artikel om dette emne paa Habr.

RIDE-kontrakten er et prædikat og returnerer "sand" eller "falsk" som output. Følgelig bliver transaktionen enten registreret i blockchain eller afvist. Den smarte kontrakt garanterer fuldt ud opfyldelsen af ​​specificerede betingelser. Det er i øjeblikket ikke muligt at generere transaktioner fra en kontrakt i RIDE.

I dag er der to typer Waves smarte kontrakter: smarte konti og smarte aktiver. En smart-konto er en almindelig brugerkonto, men der er sat et script til den, der styrer alle transaktioner. Et smart kontoscript kan f.eks. se sådan ud:

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

tx er en transaktion, der behandles, og som vi kun tillader at bruge mønstertilpasningsmekanismen, hvis det ikke er en overførselstransaktion. Mønstermatching i RIDE bruges til at kontrollere typen af ​​transaktion. Alle eksisterende konti kan behandles i det smarte kontoscript transaktionstyper.

Scriptet kan også erklære variabler, bruge "hvis-så-andet"-konstruktioner og andre metoder til fuldstændig kontrol af betingelser. For at sikre, at kontrakter har beviselig fuldstændighed og kompleksitet (omkostninger), der er nemme at forudsige, før kontraktudførelsen begynder, indeholder RIDE ikke loops eller jump-sætninger.

Andre funktioner ved Waves-konti inkluderer tilstedeværelsen af ​​en "stat", det vil sige kontoens tilstand. Du kan skrive et uendeligt antal par (nøgle, værdi) til kontotilstanden ved hjælp af datatransaktioner (DataTransaction). Disse oplysninger kan derefter behandles både gennem REST API og direkte i den smarte kontrakt.

Hver transaktion kan indeholde en række beviser, hvori deltagerens underskrift, ID'et for den nødvendige transaktion osv. kan indtastes.

Arbejder med RIDE via IDE giver dig mulighed for at se den kompilerede visning af kontrakten (hvis den er kompileret), oprette nye konti og indstille scripts til den, samt sende transaktioner via kommandolinjen.

For en fuld cyklus, inklusive oprettelse af en konto, installation af en smart kontrakt på den og afsendelse af transaktioner, kan du også bruge et bibliotek til at interagere med REST API (for eksempel C#, C, Java, JavaScript, Python, Rust, Elixir) . For at begynde at arbejde med IDE, skal du blot klikke på knappen NY.

Mulighederne for at bruge smarte kontrakter er brede: fra at forbyde transaktioner til bestemte adresser ("sortliste") til komplekse dApps.

Lad os nu se på specifikke eksempler på brugen af ​​smarte kontrakter i erhvervslivet: når der gennemføres auktioner, forsikringer og oprettes loyalitetsprogrammer.

Auktioner

En af betingelserne for en vellykket auktion er gennemsigtighed: Deltagerne skal være sikre på, at det er umuligt at manipulere bud. Dette kan opnås takket være blockchain, hvor uforanderlige data om alle væddemål og tidspunktet, hvor de blev foretaget, vil være tilgængelige for alle deltagere.

På Waves blockchain kan bud registreres i auktionskontoens tilstand via DataTransaction.

Du kan også indstille start- og sluttidspunktet for auktionen ved hjælp af bloknumre: frekvensen af ​​blokgenerering i Waves blockchain er omtrent lig med 60 sekunder.

1. Engelsk stigende prisauktion

Deltagere i en engelsk auktion byder ind i konkurrence med hinanden. Hvert nyt væddemål skal overstige det forrige. Auktionen afsluttes, når der ikke er flere budgivere, der overskrider det sidste bud. I dette tilfælde skal højestbydende oplyse det angivne beløb.

Der er også en auktionsmulighed, hvor sælger fastsætter en minimumspris for varen, og den endelige pris skal overstige denne. Ellers forbliver varen usolgt.

I dette eksempel arbejder vi med en konto, der er oprettet specifikt til auktionen. Auktionens varighed er 3000 blokke, og partiets startpris er 0,001 WAVES. En deltager kan afgive et bud ved at sende en datatransaktion med nøglen "pris" og værdien af ​​deres bud.

Prisen på det nye bud skal være højere end den aktuelle pris for denne nøgle, og deltageren skal have mindst [new_bid + kommission] tokens på sin konto. Budgiverens adresse skal noteres i feltet "afsender" i DataTransaktionen, og den aktuelle budblokhøjde skal ligge inden for auktionsperioden.

Hvis deltageren ved afslutningen af ​​auktionen har sat den højeste pris, kan han sende en ExchangeTransaction for at betale for det tilsvarende parti til den angivne pris og valutapar.

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. Hollandsk auktion over faldende priser

På en hollandsk auktion udbydes et parti i første omgang til en højere pris, end køberen er villig til at betale. Prisen falder trin for trin, indtil en af ​​deltagerne accepterer at købe varen til den aktuelle pris.

I dette eksempel bruger vi de samme konstanter som i det foregående, samt pristrinnet, når delta falder. Kontoscriptet tjekker, om deltageren faktisk er den første til at placere et væddemål. Ellers accepteres DataTransaktionen ikke af 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. Auktion "al-pay"

"All-pay" er en auktion, hvor alle deltagere betaler buddet, uanset hvem der vinder loddet. Hver ny deltager betaler et bud, og den deltager, der afgiver det maksimale bud, vinder loddet.

I vores eksempel afgiver hver auktionsdeltager et bud via DataTransaction med (nøgle, værdi)* = (“vinder”, adresse),(“pris”, pris). En sådan DataTransaktion godkendes kun, hvis denne deltager allerede har en Overførselstransaktion med sin underskrift, og hans bud er højere end alle tidligere. Auktionen fortsætter indtil sluthøjden er nået.

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

Forsikring / Crowdfunding

Lad os overveje en situation, hvor du skal sikre brugernes aktiver mod økonomiske tab. For eksempel ønsker en bruger en garanti for, at hvis et token falder, vil han være i stand til at få det fulde beløb tilbage, der er betalt for disse tokens, og er villig til at betale et rimeligt forsikringsbeløb.

For at implementere dette skal der udstedes "forsikringspoletter". Derefter installeres et script på forsikringstagerens konto, så kun de ExchangeTransactions, der opfylder visse betingelser, kan udføres.

For at forhindre dobbeltforbrug skal du anmode brugeren om at sende en DataTransaktion til forsikringstagerens konto på forhånd med (nøgle, værdi) = (purchaseTransactionId, sellOrderId) og forbyde at sende DataTransactions med en nøgle, der allerede er brugt.

Derfor skal brugerens beviser indeholde transaktions-id'et for købet af forsikringstoken. Valutaparret skal være det samme som i købstransaktionen. Prisen skal også svare til den, der er fastsat på købstidspunktet, minus forsikringsprisen.

Det er underforstået, at forsikringskontoen efterfølgende køber forsikringstokens fra brugeren til en pris, der ikke er lavere end den, han købte dem til: forsikringskontoen opretter en ExchangeTransaction, brugeren underskriver ordren (hvis transaktionen er gennemført korrekt), forsikringskonto underskriver den anden ordre og hele transaktionen og sender den til blockchain.

Hvis der ikke sker noget køb, kan brugeren oprette en ExchangeTransaction i henhold til reglerne beskrevet i scriptet og sende transaktionen til blockchain. På denne måde kan brugeren returnere pengene brugt på køb af forsikrede tokens.

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

Et forsikringstoken kan gøres til et smart aktiv, for eksempel for at forbyde dets overførsel til tredjeparter.

Denne ordning kan også implementeres for crowdfunding tokens, som returneres til ejerne, hvis det nødvendige beløb ikke er blevet indsamlet.

Transaktionsafgifter

Smarte kontrakter er også gældende i tilfælde, hvor det er nødvendigt at opkræve skat af hver transaktion med flere typer aktiver. Dette kan gøres gennem et nyt aktiv med installeret sponsorat for transaktioner med smarte aktiver:

1. Vi udsteder FeeCoin, som vil blive sendt til brugerne til en fast pris: 0,01 WAVES = 0,001 FeeCoin.

2. Indstil sponsorat for FeeCoin og vekselkurs: 0,001 WAVES = 0,001 FeeCoin.

3. Indstil følgende script for det smarte aktiv:

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
}

Nu, hver gang nogen overfører N smarte aktiver, vil de give dig FeeCoin i mængden af ​​N/taxDivisor (som kan købes hos dig på 10 *N/taxDivisor WAVES), og du vil give minearbejderen N/taxDivisor WAVES. Som et resultat vil din fortjeneste (skat) være 9*N / taxDivisor WAVES.

Du kan også udføre beskatning ved hjælp af et smart aktivscript og 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 og loyalitetsprogrammer

Cashback er en form for loyalitetsprogram, hvor køberen får en del af det brugte beløb på et produkt eller en tjeneste tilbage.

Når vi implementerer denne sag ved hjælp af en smart konto, skal vi kontrollere beviserne på samme måde, som vi gjorde i forsikringssagen. For at forhindre dobbeltforbrug skal brugeren sende en DataTransaction med (nøgle, værdi) = (purchaseTransactionId, cashbackTransactionId), før han modtager cashback.

Vi skal også sætte et forbud mod eksisterende nøgler ved hjælp af DataTransaction. cashbackDivisor - enhed divideret med cashback-andelen. De der. hvis cashback-andelen er 0.1, så 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)
}

Atombytte

Atomic swap giver brugerne mulighed for at udveksle aktiver uden hjælp fra en udveksling. Med en atomswap skal begge deltagere i transaktionen bekræfte det inden for en vis periode.

Hvis mindst en af ​​deltagerne ikke giver korrekt bekræftelse af transaktionen inden for den tid, der er afsat til transaktionen, annulleres transaktionen, og ombytningen finder ikke sted.

I vores eksempel vil vi bruge følgende smarte kontoscript:

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
}

I den næste artikel vil vi se på brugen af ​​smarte konti i finansielle instrumenter som optioner, futures og regninger.

Kilde: www.habr.com

Tilføj en kommentar