Applicazione degli smart account Waves: dalle aste ai programmi bonus

Applicazione degli smart account Waves: dalle aste ai programmi bonus

Spesso la blockchain viene associata solo alle criptovalute, ma gli ambiti di applicazione della tecnologia DLT sono molto più ampi. Uno degli ambiti più promettenti per l’utilizzo della blockchain è il contratto intelligente che viene eseguito automaticamente e non richiede fiducia tra le parti che lo stipulano.

RIDE – un linguaggio per contratti intelligenti

Waves ha sviluppato un linguaggio speciale per i contratti intelligenti: RIDE. Si trova la sua documentazione completa qui. E qui - articolo su questo argomento su Habr.

Il contratto RIDE è un predicato e restituisce “vero” o “falso” come output. Di conseguenza, la transazione viene registrata nella blockchain o rifiutata. Il contratto intelligente garantisce pienamente il rispetto delle condizioni specificate. Al momento non è possibile generare transazioni da un contratto in RIDE.

Oggi esistono due tipi di contratti intelligenti Waves: conti intelligenti e risorse intelligenti. Un account intelligente è un account utente normale, ma per esso è impostato uno script che controlla tutte le transazioni. Uno script per un account intelligente potrebbe essere simile a questo, ad esempio:

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

tx è una transazione in elaborazione che consentiamo di utilizzare il meccanismo di corrispondenza dei modelli solo se non è una transazione di trasferimento. La corrispondenza dei modelli in RIDE viene utilizzata per verificare il tipo di transazione. Tutti gli account esistenti possono essere elaborati nello script smart account tipi di transazione.

Lo script può anche dichiarare variabili, utilizzare costrutti “if-then-else” e altri metodi per verificare completamente le condizioni. Per garantire che i contratti abbiano completezza e complessità dimostrabili (costi) facili da prevedere prima dell'inizio dell'esecuzione del contratto, RIDE non contiene cicli o istruzioni di salto.

Altre caratteristiche dei conti Waves includono la presenza di uno “stato”, ovvero lo stato del conto. È possibile scrivere un numero infinito di coppie (chiave, valore) nello stato dell'account utilizzando le transazioni di dati (DataTransaction). Queste informazioni potranno poi essere elaborate sia attraverso le REST API che direttamente nello smart contract.

Ogni transazione può contenere una serie di prove in cui è possibile inserire la firma del partecipante, l'ID della transazione richiesta, ecc.

Lavorare con RIDE tramite IDE ti consente di vedere la vista compilata del contratto (se è compilato), creare nuovi account e impostare script per esso, nonché inviare transazioni tramite la riga di comando.

Per un ciclo completo, inclusa la creazione di un account, l'installazione di uno smart contract e l'invio di transazioni, puoi anche utilizzare una libreria per interagire con l'API REST (ad esempio C#, C, Java, JavaScript, Python, Rust, Elixir) . Per iniziare a lavorare con l'IDE, basta fare clic sul pulsante NUOVO.

Le possibilità di utilizzo dei contratti intelligenti sono ampie: dal divieto di transazioni verso determinati indirizzi (“lista nera”) alle dApp complesse.

Ora diamo un'occhiata ad esempi specifici dell'uso dei contratti intelligenti negli affari: quando si conducono aste, assicurazioni e si creano programmi fedeltà.

Aste

Una delle condizioni per il successo dell'asta è la trasparenza: i partecipanti devono avere la certezza che sia impossibile manipolare le offerte. Ciò può essere ottenuto grazie alla blockchain, dove i dati immutabili su tutte le scommesse e l’ora in cui sono state effettuate saranno a disposizione di tutti i partecipanti.

Sulla blockchain di Waves, le offerte possono essere registrate nello stato del conto dell'asta tramite DataTransaction.

Puoi anche impostare l'ora di inizio e fine dell'asta utilizzando i numeri dei blocchi: la frequenza di generazione dei blocchi nella blockchain di Waves è approssimativamente uguale a 60 secondi.

1. Asta inglese a prezzo ascendente

I partecipanti ad un'asta inglese fanno offerte in competizione tra loro. Ogni nuova scommessa deve superare quella precedente. L'asta termina quando non ci sono più offerenti che superino l'ultima offerta. In questo caso il miglior offerente dovrà fornire l'importo indicato.

Esiste anche un'opzione d'asta in cui il venditore fissa un prezzo minimo per il lotto e il prezzo finale deve superarlo. Altrimenti il ​​lotto resta invenduto.

In questo esempio, stiamo lavorando con un account creato appositamente per l'asta. La durata dell'asta è di 3000 blocchi e il prezzo di partenza del lotto è di 0,001 ONDE. Un partecipante può fare un'offerta inviando una DataTransaction con la chiave "prezzo" e il valore della propria offerta.

Il prezzo della nuova offerta deve essere superiore al prezzo corrente per questa chiave e il partecipante deve avere almeno [new_bid + commission] token sul suo conto. L'indirizzo dell'offerente deve essere registrato nel campo "mittente" in DataTransaction e l'altezza del blocco di offerte corrente deve rientrare nel periodo dell'asta.

Se alla fine dell'asta il partecipante ha fissato il prezzo più alto, può inviare una ExchangeTransaction per pagare il lotto corrispondente al prezzo e alla coppia di valute specificati.

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. Asta olandese a prezzi decrescenti

In un'asta olandese, inizialmente il lotto viene offerto ad un prezzo superiore a quello che l'acquirente è disposto a pagare. Il prezzo diminuisce gradualmente finché uno dei partecipanti non accetta di acquistare il lotto al prezzo corrente.

In questo esempio utilizziamo le stesse costanti del precedente, così come il passo del prezzo quando il delta diminuisce. Lo script dell'account verifica se il partecipante è effettivamente il primo a piazzare una scommessa. Altrimenti, la DataTransaction non viene accettata dalla 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. Asta “all-pay”

"All-pay" è un'asta in cui tutti i partecipanti pagano l'offerta, indipendentemente da chi vince il lotto. Ogni nuovo partecipante paga un'offerta e il partecipante che fa l'offerta massima vince il lotto.

Nel nostro esempio, ogni partecipante all'asta fa un'offerta tramite DataTransaction con (chiave, valore)* = (“vincitore”, indirizzo),(“prezzo”, prezzo). Tale DataTransaction è approvata solo se questo partecipante ha già una TransferTransaction con la sua firma e la sua offerta è più alta di tutte le precedenti. L'asta continua fino al raggiungimento della endHeight.

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

Assicurazioni/Crowdfunding

Consideriamo una situazione in cui è necessario assicurare il patrimonio degli utenti contro perdite finanziarie. Ad esempio, un utente desidera la garanzia che, se un token si svaluta, sarà in grado di recuperare l'intero importo pagato per tali token ed è disposto a pagare una ragionevole quantità di assicurazione.

Per attuare ciò, è necessario emettere “token assicurativi”. Successivamente viene installato uno script sul conto del contraente che consente l’esecuzione solo delle transazioni di scambio che soddisfano determinate condizioni.

Per evitare la doppia spesa è necessario richiedere all'utente di inviare preventivamente una DataTransaction sul conto dell'assicurato con (key, value) = (purchaseTransactionId, sellOrderId) e vietare l'invio di DataTransaction con una chiave già utilizzata.

Pertanto, le prove dell'utente devono contenere l'ID della transazione di acquisto del token assicurativo. La coppia di valute deve essere la stessa della transazione di acquisto. Il costo dovrà inoltre essere pari a quello fissato al momento dell'acquisto, detratto il prezzo dell'assicurazione.

Resta inteso che successivamente il conto assicurativo acquista i token assicurativi dall'utente ad un prezzo non inferiore a quello a cui li ha acquistati: il conto assicurativo crea una ExchangeTransaction, l'utente firma l'ordine (se la transazione viene completata correttamente), il il conto assicurativo firma il secondo ordine e l'intera transazione e lo invia alla blockchain.

Se non avviene alcun acquisto, l'utente può creare una ExchangeTransaction secondo le regole descritte nello script e inviare la transazione alla blockchain. In questo modo l'utente potrà restituire il denaro speso per l'acquisto dei token assicurati.

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

Un token assicurativo può essere reso una risorsa intelligente, ad esempio, per vietarne il trasferimento a terzi.

Questo schema può essere implementato anche per i token di crowdfunding, che vengono restituiti ai proprietari se l’importo richiesto non è stato raccolto.

Tasse sulle transazioni

I contratti intelligenti sono applicabili anche nei casi in cui è necessario riscuotere l’imposta su ciascuna transazione con diversi tipi di beni. Questo può essere fatto attraverso una nuova risorsa installata sponsorizzazione per transazioni con asset intelligenti:

1. Emettiamo FeeCoin, che verrà inviato agli utenti ad un prezzo fisso: 0,01 WAVES = 0,001 FeeCoin.

2. Imposta la sponsorizzazione per FeeCoin e il tasso di cambio: 0,001 WAVES = 0,001 FeeCoin.

3. Impostare il seguente script per la risorsa intelligente:

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
}

Ora ogni volta che qualcuno trasferisce N asset intelligenti, ti darà FeeCoin per un importo di N/taxDivisor (che può essere acquistato da te a 10 *N/taxDivisor WAVES) e tu darai al minatore N/taxDivisor WAVES. Di conseguenza, il tuo profitto (tasse) sarà 9*N / taxDivisor WAVES.

Puoi anche eseguire la tassazione utilizzando uno script di risorse intelligenti e 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 e programmi fedeltà

Il cashback è un tipo di programma fedeltà in cui l'acquirente recupera parte dell'importo speso per un prodotto o servizio.

Quando implementiamo questo caso utilizzando uno smart account, dobbiamo controllare le prove nello stesso modo in cui abbiamo fatto nel caso dell'assicurazione. Per evitare una doppia spesa, l'utente deve inviare una DataTransaction con (chiave, valore) = (purchaseTransactionId, cashbackTransactionId) prima di ricevere il cashback.

Dobbiamo anche vietare le chiavi esistenti utilizzando DataTransaction. cashbackDivisor - unità divisa per la quota di cashback. Quelli. se la quota del cashback è 0.1, allora 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)
}

Scambio atomico

Lo scambio atomico consente agli utenti di scambiare risorse senza l'aiuto di uno scambio. Nell'Atomic Swap entrambi i partecipanti alla transazione sono tenuti a confermarla entro un certo periodo di tempo.

Se almeno uno dei partecipanti non fornisce la corretta conferma della transazione entro il tempo previsto per la transazione, la transazione viene annullata e lo scambio non avviene.

Nel nostro esempio, utilizzeremo il seguente script smart account:

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
}

Nel prossimo articolo esamineremo l'utilizzo degli smart account in strumenti finanziari come opzioni, futures e fatture.

Fonte: habr.com

Aggiungi un commento