Tillämpning av Waves smarta konton: från auktioner till bonusprogram

Tillämpning av Waves smarta konton: från auktioner till bonusprogram

Blockchain förknippas ofta enbart med kryptovalutor, men användningsområdena för DLT-teknik är mycket bredare. Ett av de mest lovande områdena för användningen av blockchain är ett smart kontrakt som exekveras automatiskt och inte kräver förtroende mellan parterna som ingått det.

RIDE – ett språk för smarta kontrakt

Waves har utvecklat ett speciellt språk för smarta kontrakt – RIDE. Dess fullständiga dokumentation finns här. Och här - artikel om detta ämne på Habr.

RIDE-kontraktet är ett predikat och returnerar "true" eller "false" som utdata. Följaktligen registreras transaktionen antingen i blockkedjan eller avvisas. Det smarta kontraktet garanterar fullt ut uppfyllandet av specificerade villkor. Att generera transaktioner från ett kontrakt i RIDE är för närvarande inte möjligt.

Idag finns det två typer av Waves smarta kontrakt: smarta konton och smarta tillgångar. Ett smart konto är ett vanligt användarkonto, men ett script är satt för det som kontrollerar alla transaktioner. Ett smart kontoskript kan se ut så här, till exempel:

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

tx är en transaktion som bearbetas och som vi tillåter att använda mönstermatchningsmekanismen endast om det inte är en överföringstransaktion. Mönstermatchning i RIDE används för att kontrollera typen av transaktion. Alla befintliga konton kan bearbetas i det smarta kontoskriptet transaktionstyper.

Skriptet kan också deklarera variabler, använda "if-then-else"-konstruktioner och andra metoder för att fullständigt kontrollera villkoren. För att säkerställa att kontrakt har bevisbar fullständighet och komplexitet (kostnad) som är lätt att förutse innan kontraktsutförande börjar, innehåller RIDE inga loopar eller jump-satser.

Andra funktioner hos Waves-konton inkluderar närvaron av en "tillstånd", det vill säga kontots tillstånd. Du kan skriva ett oändligt antal par (nyckel, värde) till kontotillståndet med hjälp av datatransaktioner (DataTransaction). Denna information kan sedan bearbetas både genom REST API och direkt i det smarta kontraktet.

Varje transaktion kan innehålla en rad bevis, i vilka deltagarens signatur, ID för den önskade transaktionen etc. kan skrivas in.

Arbeta med RIDE via IDE låter dig se den kompilerade vyn av kontraktet (om det är kompilerat), skapa nya konton och ställa in skript för det, samt skicka transaktioner via kommandoraden.

För en hel cykel, inklusive att skapa ett konto, installera ett smart kontrakt på det och skicka transaktioner, kan du också använda ett bibliotek för att interagera med REST API (till exempel C#, C, Java, JavaScript, Python, Rust, Elixir) . För att börja arbeta med IDE, klicka bara på knappen NY.

Möjligheterna att använda smarta kontrakt är breda: från att förbjuda transaktioner till vissa adresser ("svarta listan") till komplexa dApps.

Låt oss nu titta på specifika exempel på användningen av smarta kontrakt i affärer: när man genomför auktioner, försäkringar och skapar lojalitetsprogram.

Auktioner

Ett av villkoren för en framgångsrik auktion är transparens: deltagarna måste vara säkra på att det är omöjligt att manipulera bud. Detta kan uppnås tack vare blockkedjan, där oföränderlig data om alla insatser och tidpunkten när de gjordes kommer att vara tillgänglig för alla deltagare.

På Waves blockchain kan bud registreras i auktionskontostatus via DataTransaction.

Du kan också ställa in start- och sluttid för auktionen med blocknummer: frekvensen av blockgenerering i Waves blockchain är ungefär lika med 60 sekunder.

1. Engelsk auktion med stigande pris

Deltagare i en engelsk auktion lägger bud i konkurrens med varandra. Varje ny insats måste överstiga den föregående. Auktionen avslutas när det inte finns fler budgivare som överskrider det sista budet. I detta fall måste högstbjudande lämna det angivna beloppet.

Det finns också en auktionsmöjlighet där säljaren sätter ett minimipris för varan, och slutpriset måste överstiga det. Annars förblir varan osåld.

I det här exemplet arbetar vi med ett konto som skapats specifikt för auktionen. Auktionens varaktighet är 3000 block, och utgångspriset för partiet är 0,001 VÅGOR. En deltagare kan lägga ett bud genom att skicka en datatransaktion med nyckeln "pris" och värdet på deras bud.

Priset på det nya budet måste vara högre än det aktuella priset för denna nyckel, och deltagaren måste ha minst [new_bid + commission] tokens på sitt konto. Budgivarens adress måste antecknas i fältet "avsändare" i DataTransaktionen och den aktuella budblockshöjden måste ligga inom auktionsperioden.

Om deltagaren i slutet av auktionen har satt det högsta priset, kan han skicka en ExchangeTransaction för att betala för motsvarande lot till angivet pris och 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. Holländsk auktion av sjunkande priser

På en holländsk auktion erbjuds inledningsvis mycket till ett pris som är högre än vad köparen är villig att betala. Priset minskar steg för steg tills en av deltagarna går med på att köpa varan till aktuellt pris.

I det här exemplet använder vi samma konstanter som i det föregående, samt prissteget när delta minskar. Kontoskriptet kontrollerar om deltagaren verkligen är den första som lägger ett vad. Annars accepteras inte datatransaktionen av blockkedjan.

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 "all-pay"

"All-pay" är en auktion där alla deltagare betalar budet, oavsett vem som vinner lotten. Varje ny deltagare betalar ett bud, och den deltagare som lägger det högsta budet vinner lotten.

I vårt exempel lägger varje auktionsdeltagare ett bud via DataTransaction med (nyckel, värde)* = (“vinnare”, adress),(“pris”, pris). En sådan DataTransaktion godkänns endast om denna deltagare redan har en TransferTransaction med sin signatur och hans bud är högre än alla tidigare. Auktionen fortsätter tills sluthöjden uppnås.

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

Försäkring / Crowdfunding

Låt oss överväga en situation där du behöver försäkra användarnas tillgångar mot ekonomiska förluster. En användare vill till exempel ha en garanti för att om en token sjunker, kommer han att kunna få tillbaka hela beloppet som betalats för dessa tokens, och är villig att betala en rimlig mängd försäkring.

För att implementera detta måste "försäkringspoletter" utfärdas. Sedan installeras ett skript på försäkringstagarens konto, vilket gör att endast de ExchangeTransactions som uppfyller vissa villkor kan utföras.

För att förhindra dubbla utgifter måste du begära att användaren skickar en DataTransaktion till försäkringstagarens konto i förväg med (nyckel, värde) = (purchaseTransactionId, sellOrderId) och förbjuda att skicka DataTransactions med en nyckel som redan har använts.

Därför måste användarens bevis innehålla transaktions-ID för köpet av försäkringstoken. Valutaparet måste vara detsamma som i köptransaktionen. Kostnaden ska också vara lika med den som fastställdes vid köptillfället, minus priset på försäkringen.

Det är underförstått att försäkringskontot därefter köper försäkringstokens från användaren till ett pris som inte är lägre än det till vilket han köpte dem: försäkringskontot skapar en ExchangeTransaction, användaren undertecknar ordern (om transaktionen genomförs korrekt), försäkringskonto signerar den andra ordern och hela transaktionen och skickar den till blockkedjan.

Om inget köp sker kan användaren skapa en ExchangeTransaction enligt reglerna som beskrivs i skriptet och skicka transaktionen till blockkedjan. På så sätt kan användaren returnera pengarna som spenderats på köp av försäkrade 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)
}

En försäkringstoken kan göras till en smart tillgång, till exempel för att förbjuda dess överföring till tredje part.

Detta system kan också implementeras för crowdfunding-tokens, som returneras till ägarna om det erforderliga beloppet inte har samlats in.

Transaktionsskatter

Smarta kontrakt är också tillämpliga i de fall det är nödvändigt att ta ut skatt på varje transaktion med flera typer av tillgångar. Detta kan göras genom en ny tillgång med installerad sponsorskap för transaktioner med smarta tillgångar:

1. Vi utfärdar FeeCoin, som kommer att skickas till användare till ett fast pris: 0,01 WAVES = 0,001 FeeCoin.

2. Ställ in sponsring för FeeCoin och växelkurs: 0,001 WAVES = 0,001 FeeCoin.

3. Ställ in följande skript för den smarta tillgången:

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 varje gång någon överför N smarta tillgångar, kommer de att ge dig FeeCoin i mängden N/taxDivisor (som kan köpas från dig för 10 *N/taxDivisor WAVES), och du kommer att ge gruvarbetaren N/taxDivisor WAVES. Som ett resultat kommer din vinst (skatt) att vara 9*N / taxDivisor WAVES.

Du kan också utföra beskattning med hjälp av ett smart tillgångsskript och 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 och lojalitetsprogram

Cashback är en typ av lojalitetsprogram där köparen får tillbaka en del av det belopp som spenderats på en produkt eller tjänst.

När vi implementerar detta ärende med ett smart konto måste vi kontrollera bevisen på samma sätt som vi gjorde i försäkringsfallet. För att förhindra dubbla utgifter måste användaren skicka en DataTransaction med (nyckel, värde) = (purchaseTransactionId, cashbackTransactionId) innan han får cashback.

Vi måste också sätta ett förbud mot befintliga nycklar med hjälp av DataTransaction. cashbackDivisor - enhet dividerat med cashback-andelen. De där. om cashback-andelen är 0.1 så är 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)
}

Atombyte

Atomic swap tillåter användare att byta tillgångar utan hjälp av ett utbyte. Med ett atombyte måste båda deltagarna i transaktionen bekräfta det inom en viss tidsperiod.

Om minst en av deltagarna inte ger korrekt bekräftelse på transaktionen inom den tid som avsatts för transaktionen, avbryts transaktionen och bytet sker inte.

I vårt exempel kommer vi att använda följande smarta kontoskript:

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 nästa artikel kommer vi att titta på användningen av smarta konton i finansiella instrument som optioner, terminer och växlar.

Källa: will.com

Lägg en kommentar