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