Bruk av Waves smartkontoer: fra auksjoner til bonusprogrammer

Bruk av Waves smartkontoer: fra auksjoner til bonusprogrammer

Blokkjede forbindes ofte kun med kryptovalutaer, men bruksområdene til DLT-teknologi er mye bredere. Et av de mest lovende områdene for bruk av blokkjede er en smart kontrakt som utføres automatisk og som ikke krever tillit mellom partene som har inngått den.

RIDE – et språk for smarte kontrakter

Waves har utviklet et spesielt språk for smarte kontrakter – RIDE. Den fullstendige dokumentasjonen er plassert her. Og her - artikkel om dette emnet på Habr.

RIDE-kontrakten er et predikat og returnerer "true" eller "false" som utdata. Følgelig blir transaksjonen enten registrert i blokkjeden eller avvist. Den smarte kontrakten garanterer fullt ut oppfyllelsen av spesifiserte betingelser. Generering av transaksjoner fra en kontrakt i RIDE er foreløpig ikke mulig.

I dag finnes det to typer Waves smarte kontrakter: smarte kontoer og smarte eiendeler. En smartkonto er en vanlig brukerkonto, men det er satt et script for den som kontrollerer alle transaksjoner. Et smart kontoskript kan se slik ut, for eksempel:

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

tx er en transaksjon som behandles og vi tillater bruk av mønstertilpasningsmekanismen bare hvis det ikke er en overføringstransaksjon. Mønstertilpasning i RIDE brukes til å sjekke transaksjonstypen. Alle eksisterende kontoer kan behandles i smartkontoskriptet transaksjonstyper.

Skriptet kan også deklarere variabler, bruke "hvis-så-annet"-konstruksjoner og andre metoder for fullstendig kontroll av forholdene. For å sikre at kontrakter har beviselig fullstendighet og kompleksitet (kostnad) som er enkle å forutsi før kontraktsutførelse begynner, inneholder ikke RIDE loops eller jump-setninger.

Andre funksjoner ved Waves-kontoer inkluderer tilstedeværelsen av en "stat", det vil si tilstanden til kontoen. Du kan skrive et uendelig antall par (nøkkel, verdi) til kontotilstanden ved å bruke datatransaksjoner (DataTransaction). Denne informasjonen kan deretter behandles både gjennom REST API og direkte i smartkontrakten.

Hver transaksjon kan inneholde en rekke bevis, der signaturen til deltakeren, ID-en til den nødvendige transaksjonen osv. kan legges inn.

Arbeider med RIDE via IDE lar deg se den kompilerte visningen av kontrakten (hvis den er kompilert), opprette nye kontoer og angi skript for den, samt sende transaksjoner via kommandolinjen.

For en full syklus, inkludert å opprette en konto, installere en smart kontrakt på den og sende transaksjoner, kan du også bruke et bibliotek for å samhandle med REST API (for eksempel C#, C, Java, JavaScript, Python, Rust, Elixir) . For å begynne å jobbe med IDE, klikker du bare på NEW-knappen.

Mulighetene for å bruke smarte kontrakter er brede: fra å forby transaksjoner til bestemte adresser ("svarteliste") til komplekse dApps.

La oss nå se på spesifikke eksempler på bruk av smarte kontrakter i næringslivet: når du gjennomfører auksjoner, forsikring og oppretter lojalitetsprogrammer.

Auksjoner

En av betingelsene for en vellykket auksjon er åpenhet: Deltakerne må være trygge på at det er umulig å manipulere bud. Dette kan oppnås takket være blokkjeden, hvor uforanderlige data om alle spill og tidspunktet da de ble gjort vil være tilgjengelig for alle deltakere.

På Waves blockchain kan bud registreres i auksjonskontostatus via DataTransaction.

Du kan også angi start- og sluttid for auksjonen ved å bruke blokknummer: frekvensen av blokkgenerering i Waves blokkjede er omtrent lik 60 sekunder.

1. Engelsk auksjon med stigende priser

Deltakere i en engelsk auksjon legger inn bud i konkurranse med hverandre. Hvert nye spill må overstige det forrige. Auksjonen avsluttes når det ikke er flere budgivere som overskrider siste bud. I dette tilfellet må høystbydende oppgi det oppgitte beløpet.

Det er også en auksjonsmulighet der selgeren setter en minstepris for partiet, og sluttprisen må overstige denne. Ellers forblir partiet usolgt.

I dette eksemplet jobber vi med en konto spesielt opprettet for auksjonen. Auksjonens varighet er 3000 blokker, og startprisen på partiet er 0,001 WAVES. En deltaker kan legge inn et bud ved å sende en datatransaksjon med nøkkelen "pris" og verdien av budet.

Prisen på det nye budet må være høyere enn gjeldende pris for denne nøkkelen, og deltakeren må ha minst [nytt_bud + provisjon] tokens på kontoen sin. Budgivers adresse må registreres i "avsender"-feltet i DataTransaksjonen, og gjeldende budblokkhøyde må være innenfor auksjonsperioden.

Hvis deltakeren på slutten av auksjonen har satt den høyeste prisen, kan han sende en ExchangeTransaction for å betale for det tilsvarende partiet til spesifisert 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. Nederlandsk auksjon av synkende priser

I en nederlandsk auksjon tilbys i utgangspunktet mye til en pris som er høyere enn hva kjøperen er villig til å betale. Prisen reduseres trinnvis inntil en av deltakerne sier ja til å kjøpe partiet til gjeldende pris.

I dette eksemplet bruker vi de samme konstantene som i forrige, samt pristrinnet når delta minker. Kontoskriptet sjekker om deltakeren faktisk er den første som legger et spill. Ellers aksepteres ikke datatransaksjonen av blokkjeden.

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. Auksjon «all-pay»

"All-pay" er en auksjon der alle deltakere betaler budet, uavhengig av hvem som vinner partiet. Hver ny deltaker betaler et bud, og deltakeren som gir det høyeste budet vinner partiet.

I vårt eksempel legger hver auksjonsdeltaker et bud via DataTransaction med (nøkkel, verdi)* = (“vinner”, adresse),(“pris”, pris). En slik DataTransaksjon godkjennes bare hvis denne deltakeren allerede har en Overføringstransaksjon med sin signatur og hans bud er høyere enn alle tidligere. Auksjonen fortsetter til slutthøyden er nådd.

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

La oss vurdere en situasjon der du må forsikre brukernes eiendeler mot økonomiske tap. En bruker vil for eksempel ha en garanti for at hvis et token faller, vil han kunne få tilbake hele beløpet som er betalt for disse tokenene, og er villig til å betale en rimelig forsikringssum.

For å implementere dette må det utstedes "forsikringstegn". Deretter installeres et skript på forsikringstakers konto, slik at bare de ExchangeTransactions som oppfyller visse betingelser kan utføres.

For å forhindre dobbeltforbruk, må du be brukeren om å sende en DataTransaksjon til forsikringstakers konto på forhånd med (nøkkel, verdi) = (purchaseTransactionId, sellOrderId) og forby sending av DataTransactions med en nøkkel som allerede er brukt.

Derfor må brukerens bevis inneholde transaksjons-IDen til kjøpet av forsikringssymbolet. Valutaparet må være det samme som i kjøpstransaksjonen. Kostnaden må også være lik den som er fastsatt på kjøpstidspunktet, minus forsikringsprisen.

Det er forstått at forsikringskontoen deretter kjøper forsikringssymboler fra brukeren til en pris som ikke er lavere enn den han kjøpte dem til: forsikringskontoen oppretter en ExchangeTransaction, brukeren signerer bestillingen (hvis transaksjonen er fullført riktig), forsikringskonto signerer den andre ordren og hele transaksjonen og sender den til blokkjeden.

Hvis det ikke skjer noe kjøp, kan brukeren opprette en ExchangeTransaction i henhold til reglene beskrevet i skriptet og sende transaksjonen til blokkjeden. På denne måten kan brukeren returnere pengene brukt på kjøp av 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 gjøres til en smart eiendel, for eksempel for å forby overføringen til tredjeparter.

Denne ordningen kan også implementeres for crowdfunding-tokens, som returneres til eierne dersom det nødvendige beløpet ikke er samlet inn.

Transaksjonsavgifter

Smarte kontrakter er også aktuelt i tilfeller der det er nødvendig å kreve inn skatt på hver transaksjon med flere typer eiendeler. Dette kan gjøres gjennom et nytt aktivum med installert sponsoravtale for transaksjoner med smarte eiendeler:

1. Vi utsteder FeeCoin, som vil bli sendt til brukere til en fast pris: 0,01 WAVES = 0,001 FeeCoin.

2. Angi sponsing for FeeCoin og valutakurs: 0,001 WAVES = 0,001 FeeCoin.

3. Angi følgende skript for smartelementet:

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
}

Nå hver gang noen overfører N smarte eiendeler, vil de gi deg FeeCoin i mengden N/taxDivisor (som kan kjøpes fra deg til 10 *N/taxDivisor WAVES), og du vil gi gruvearbeideren N/taxDivisor WAVES. Som et resultat vil fortjenesten (skatten) din være 9*N / taxDivisor WAVES.

Du kan også utføre skattlegging ved å bruke et smart aktivaskript 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 lojalitetsprogrammer

Cashback er en type lojalitetsprogram der kjøperen får tilbake deler av beløpet som er brukt på et produkt eller en tjeneste.

Ved implementering av denne saken ved bruk av smartkonto, må vi kontrollere bevisene på samme måte som vi gjorde i forsikringssaken. For å forhindre dobbeltforbruk må brukeren sende en DataTransaction med (nøkkel, verdi) = (purchaseTransactionId, cashbackTransactionId) før han mottar cashback.

Vi må også sette et forbud mot eksisterende nøkler ved bruk av DataTransaction. cashbackDivisor - enhet delt på cashback-andelen. De. 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 lar brukere bytte eiendeler uten hjelp av en utveksling. Med en atombytte må begge deltakerne i transaksjonen bekrefte det innen en viss tidsperiode.

Dersom minst en av deltakerne ikke gir korrekt bekreftelse på transaksjonen innen den tid som er avsatt for transaksjonen, kanselleres transaksjonen og utvekslingen finner ikke sted.

I vårt eksempel vil vi bruke følgende smartkontoskript:

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 neste artikkel skal vi se på bruken av smartkontoer i finansielle instrumenter som opsjoner, futures og veksler.

Kilde: www.habr.com

Legg til en kommentar