Aplicació de comptes intel·ligents Waves: des de subhastes fins a programes de bonificació

Aplicació de comptes intel·ligents Waves: des de subhastes fins a programes de bonificació

La cadena de blocs sovint s'associa només amb criptomonedes, però les àrees d'aplicació de la tecnologia DLT són molt més àmplies. Una de les àrees més prometedores per a l'ús de la cadena de blocs és un contracte intel·ligent que s'executa automàticament i no requereix confiança entre les parts que l'han subscrit.

RIDE: un llenguatge per a contractes intel·ligents

Waves ha desenvolupat un llenguatge especial per a contractes intel·ligents: RIDE. Es troba la seva documentació completa aquí. I aquí - article sobre aquest tema a Habr.

El contracte RIDE és un predicat i retorna "true" o "fals" com a sortida. En conseqüència, la transacció es registra a la cadena de blocs o es rebutja. El contracte intel·ligent garanteix totalment el compliment de les condicions especificades. Actualment no és possible generar transaccions a partir d'un contracte a RIDE.

Avui dia hi ha dos tipus de contractes intel·ligents Waves: els comptes intel·ligents i els actius intel·ligents. Un compte intel·ligent és un compte d'usuari normal, però s'hi ha establert un script que controla totes les transaccions. Un script de compte intel·ligent podria semblar així, per exemple:

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

tx és una transacció que s'està processant que permetem utilitzar el mecanisme de concordança de patrons només si no és una transacció de transferència. La concordança de patró a RIDE s'utilitza per comprovar el tipus de transacció. Tots els comptes existents es poden processar a l'script de compte intel·ligent tipus de transaccions.

L'script també pot declarar variables, utilitzar construccions "si-aleshores-else" i altres mètodes per comprovar completament les condicions. Per garantir que els contractes tinguin una exhaustivitat demostrable i una complexitat (cost) fàcil de predir abans que comenci l'execució del contracte, RIDE no conté bucles ni declaracions de salt.

Altres característiques dels comptes de Waves inclouen la presència d'un "estat", és a dir, l'estat del compte. Podeu escriure un nombre infinit de parells (clau, valor) a l'estat del compte mitjançant transaccions de dades (DataTransaction). Aquesta informació es pot processar tant a través de l'API REST com directament al contracte intel·ligent.

Cada transacció pot contenir una sèrie de proves, en les quals es pot introduir la signatura del participant, l'identificador de la transacció requerida, etc.

Treballant amb RIDE via IDE us permet veure la vista compilada del contracte (si està compilat), crear nous comptes i establir-hi scripts, així com enviar transaccions mitjançant la línia d'ordres.

Per a un cicle complet, inclosa la creació d'un compte, la instal·lació d'un contracte intel·ligent i l'enviament de transaccions, també podeu utilitzar una biblioteca per interactuar amb l'API REST (per exemple, C#, C, Java, JavaScript, Python, Rust, Elixir) . Per començar a treballar amb l'IDE, només cal que feu clic al botó NOU.

Les possibilitats d'utilitzar contractes intel·ligents són àmplies: des de prohibir transaccions a determinades adreces ("llista negra") fins a dApps complexes.

Vegem ara exemples concrets de l'ús de contractes intel·ligents en els negocis: quan es realitzen subhastes, assegurances i es creen programes de fidelització.

Subhastes

Una de les condicions per a una subhasta reeixida és la transparència: els participants han de confiar que és impossible manipular les ofertes. Això es pot aconseguir gràcies a la cadena de blocs, on les dades immutables sobre totes les apostes i el moment en què es van fer estaran disponibles per a tots els participants.

A la cadena de blocs Waves, les ofertes es poden registrar a l'estat del compte de subhasta mitjançant DataTransaction.

També podeu establir les hores d'inici i finalització de la subhasta mitjançant números de bloc: la freqüència de generació de blocs a la cadena de blocs Waves és aproximadament igual a 60 segons.

1. Subhasta de preu ascendent en anglès

Els participants en una subhasta anglesa posen ofertes en competència entre ells. Cada nova aposta ha de superar l'anterior. La subhasta acaba quan no hi ha més licitadors que superin l'última oferta. En aquest cas, el millor postor haurà d'aportar l'import indicat.

També hi ha una opció de subhasta en la qual el venedor estableix un preu mínim per al lot, i el preu final ha de superar-lo. En cas contrari, el lot romandrà sense vendre.

En aquest exemple, estem treballant amb un compte creat específicament per a la subhasta. La durada de la subhasta és de 3000 blocs, i el preu inicial del lot és de 0,001 ONDES. Un participant pot fer una oferta enviant una transacció de dades amb la clau "preu" i el valor de la seva oferta.

El preu de la nova oferta ha de ser superior al preu actual d'aquesta clau i el participant ha de tenir almenys [new_bid + comissió] fitxes al seu compte. L'adreça del licitador s'ha d'enregistrar al camp "remitent" de DataTransaction i l'alçada actual del bloc d'oferta ha d'estar dins del període de subhasta.

Si al final de la subhasta el participant ha establert el preu més alt, pot enviar una transacció d'intercanvi per pagar el lot corresponent al preu i parell de divises especificats.

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. Subhasta holandesa de preus decreixents

En una subhasta holandesa, inicialment s'ofereix un lot a un preu superior al que el comprador està disposat a pagar. El preu disminueix pas a pas fins que un dels participants es compromet a comprar el lot al preu actual.

En aquest exemple utilitzem les mateixes constants que en l'anterior, així com el pas de preu quan el delta disminueix. L'script del compte comprova si el participant és realment el primer a apostar. En cas contrari, la transacció de dades no és acceptada per la cadena de blocs.

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. Subhasta "tot pagament"

"All-pay" és una subhasta en què tots els participants paguen l'oferta, independentment de qui guanyi el lot. Cada nou participant paga una oferta, i el participant que faci l'oferta màxima guanya el lot.

En el nostre exemple, cada participant de la subhasta fa una oferta mitjançant DataTransaction amb (clau, valor)* = ("guanyador", adreça), ("preu", preu). Aquesta transacció de dades només s'aprova si aquest participant ja té una transacció de transferència amb la seva signatura i la seva oferta és superior a totes les anteriors. La subhasta continua fins que s'arriba a l'alçada final.

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

Assegurança / Crowdfunding

Considerem una situació en què necessiteu assegurar els actius dels usuaris contra pèrdues financeres. Per exemple, un usuari vol una garantia que si un testimoni es deprecia, podrà recuperar l'import total pagat per aquests fitxes i està disposat a pagar una quantitat raonable d'assegurança.

Per implementar-ho, cal emetre "fitxes d'assegurança". A continuació, s'instal·la un script al compte del prenedor de la pòlissa, que només permet executar aquelles transaccions d'intercanvi que compleixen determinades condicions.

Per evitar la doble despesa, heu de sol·licitar a l'usuari que enviï una transacció de dades al compte del prenedor de l'assegurança amb (clau, valor) = (identificador de transacció de compra, identificació de comanda de venda) i prohibir l'enviament de transaccions de dades amb una clau que ja s'ha utilitzat.

Per tant, les proves de l'usuari han de contenir l'identificador de la transacció de la compra del token d'assegurança. El parell de divises ha de ser el mateix que en la transacció de compra. El cost també ha de ser igual al fixat en el moment de la compra, menys el preu de l'assegurança.

S'entén que posteriorment el compte d'assegurança compra a l'usuari fitxes d'assegurança a un preu no inferior al que els va comprar: el compte d'assegurança crea una transacció d'intercanvi, l'usuari signa la comanda (si la transacció es realitza correctament), el El compte d'assegurança signa la segona comanda i tota la transacció i l'envia a la cadena de blocs.

Si no es produeix cap compra, l'usuari pot crear una transacció d'intercanvi segons les regles descrites a l'script i enviar la transacció a la cadena de blocs. D'aquesta manera l'usuari pot retornar els diners gastats en la compra de fitxes assegurades.

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 testimoni d'assegurança es pot convertir en un actiu intel·ligent, per exemple, per prohibir-ne la transferència a tercers.

Aquest esquema també es pot implementar per a fitxes de crowdfunding, que es retornen als propietaris si no s'ha cobrat la quantitat requerida.

Impostos sobre transaccions

Els contractes intel·ligents també són aplicables en els casos en què cal recaptar l'impost sobre cada transacció amb diversos tipus d'actius. Això es pot fer mitjançant un nou actiu amb instal·lat patrocini per a transaccions amb actius intel·ligents:

1. Emetem FeeCoin, que s'enviarà als usuaris a un preu fix: 0,01 WAVES = 0,001 FeeCoin.

2. Establiu el patrocini de FeeCoin i el tipus de canvi: 0,001 WAVES = 0,001 FeeCoin.

3. Configureu l'script següent per a l'actiu intel·ligent:

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
}

Ara, cada vegada que algú transfereixi N actius intel·ligents, us donarà FeeCoin per la quantitat de N/taxDivisor (que us podeu comprar a 10 *N/taxDivisor WAVES), i li donareu al miner N/taxDivisor WAVES. Com a resultat, el vostre benefici (impost) serà de 9*N / taxDivisor WAVES.

També podeu fer impostos mitjançant un script d'actius intel·ligents i 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
}

Programes de devolució i fidelització

El cashback és un tipus de programa de fidelització en què el comprador recupera part de l'import gastat en un producte o servei.

Quan implementem aquest cas mitjançant un compte intel·ligent, hem de comprovar les proves de la mateixa manera que ho vam fer en el cas de l'assegurança. Per evitar la doble despesa, l'usuari ha d'enviar una DataTransaction amb (clau, valor) = (purchaseTransactionId, cashbackTransactionId) abans de rebre la devolució.

També hem de prohibir les claus existents mitjançant DataTransaction. cashbackDivisor - unitat dividida per la quota de devolució. Aquells. si la quota de devolució és de 0.1, aleshores 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)
}

Intercanvi atòmic

L'intercanvi atòmic permet als usuaris intercanviar actius sense l'ajuda d'un intercanvi. Amb un intercanvi atòmic, els dos participants en la transacció han de confirmar-ho en un període de temps determinat.

Si almenys un dels participants no proporciona la confirmació correcta de la transacció dins del temps assignat per a la transacció, la transacció es cancel·la i l'intercanvi no es produeix.

Al nostre exemple, utilitzarem l'script de compte intel·ligent següent:

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
}

En el següent article analitzarem l'ús de comptes intel·ligents en instruments financers com opcions, futurs i factures.

Font: www.habr.com

Afegeix comentari