Aplicacións das contas intelixentes Waves: desde poxas ata programas de bonificación

Aplicacións das contas intelixentes Waves: desde poxas ata programas de bonificación

A cadea de bloques adoita asociarse só con criptomoedas, pero as áreas de aplicación da tecnoloxía DLT son moito máis amplas. Unha das áreas máis prometedoras para o uso da cadea de bloques é un contrato intelixente que se executa automaticamente e non require confianza entre as partes que o entraron.

RIDE: unha linguaxe para contratos intelixentes

Waves desenvolveu unha linguaxe especial para contratos intelixentes: RIDE. Atópase a súa documentación completa aquí. E aquí - artigo sobre este tema en Habr.

O contrato RIDE é un predicado e devolve "verdadeiro" ou "falso" como saída. En consecuencia, a transacción rexístrase na cadea de bloques ou rexéitase. O contrato intelixente garante plenamente o cumprimento das condicións especificadas. Actualmente non é posible xerar transaccións a partir dun contrato en RIDE.

Hoxe hai dous tipos de contratos intelixentes de Waves: contas intelixentes e activos intelixentes. Unha conta intelixente é unha conta de usuario normal, pero establécese un script que controla todas as transaccións. Un script de conta intelixente pode verse así, por exemplo:

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

tx é unha transacción en proceso de procesamento que permitimos usar o mecanismo de coincidencia de patróns só se non é unha transacción de transferencia. A coincidencia de patróns en RIDE úsase para comprobar o tipo de transacción. Todas as contas existentes pódense procesar no script de conta intelixente tipos de transacción.

O script tamén pode declarar variables, usar construcións "if-then-else" e outros métodos para comprobar completamente as condicións. Para garantir que os contratos teñan unha integridade e unha complexidade (custo) demostrables que sexan fáciles de predecir antes de que comece a execución do contrato, RIDE non contén bucles nin declaracións de salto.

Outras características das contas de Waves inclúen a presenza dun "estado", é dicir, o estado da conta. Pode escribir un número infinito de pares (clave, valor) no estado da conta mediante transaccións de datos (DataTransaction). Esta información pódese procesar a través da API REST e directamente no contrato intelixente.

Cada transacción pode conter unha serie de probas nas que se pode introducir a sinatura do participante, o ID da transacción requirida, etc.

Traballando con RIDE via IDE permítelle ver a vista compilada do contrato (se está compilado), crear novas contas e establecer scripts para el, así como enviar transaccións a través da liña de comandos.

Para un ciclo completo, incluíndo a creación dunha conta, a instalación dun contrato intelixente e o envío de transaccións, tamén podes usar unha biblioteca para interactuar coa API REST (por exemplo, C#, C, Java, JavaScript, Python, Rust, Elixir) . Para comezar a traballar co IDE, só tes que premer no botón NOVO.

As posibilidades de usar contratos intelixentes son amplas: desde prohibir transaccións a determinados enderezos ("lista negra") ata dApps complexas.

Vexamos agora exemplos específicos do uso de contratos intelixentes nos negocios: cando se realizan poxas, seguros e se crean programas de fidelización.

Poxas

Unha das condicións para unha poxa exitosa é a transparencia: os participantes deben estar seguros de que é imposible manipular as ofertas. Isto pódese conseguir grazas á cadea de bloques, onde estarán dispoñibles para todos os participantes datos inmutables sobre todas as apostas e o momento en que se realizaron.

Na cadea de bloques de Waves, as ofertas pódense rexistrar no estado da conta de poxa a través de DataTransaction.

Tamén pode establecer a hora de inicio e finalización da poxa usando números de bloque: a frecuencia de xeración de bloques na cadea de bloques de Waves é aproximadamente igual a 60 segundos.

1. Poxa de prezos ascendentes en inglés

Os participantes nunha poxa inglesa presentan ofertas en competencia entre si. Cada aposta nova debe superar a anterior. A poxa remata cando non haxa máis licitadores que superen a última oferta. Neste caso, o mellor postor deberá achegar o importe indicado.

Tamén existe unha opción de poxa na que o vendedor fixa un prezo mínimo para o lote, debendo superar o prezo final. En caso contrario, o lote permanece sen vender.

Neste exemplo, estamos a traballar cunha conta creada especificamente para a poxa. A duración da poxa é de 3000 bloques e o prezo inicial do lote é de 0,001 ONDAS. Un participante pode facer unha oferta enviando unha DataTransaction coa clave "prezo" e o valor da súa oferta.

O prezo da nova oferta debe ser superior ao prezo actual desta clave e o participante debe ter polo menos [new_bid + comisión] fichas na súa conta. O enderezo do licitador debe rexistrarse no campo "remitente" de DataTransaction e a altura actual do bloque de ofertas debe estar dentro do período de poxa.

Se ao final da poxa o participante estableceu o prezo máis alto, pode enviar unha Transacción de cambio para pagar o lote correspondente ao prezo e ao par de divisas especificados.

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. Poxa holandesa de prezos decrecentes

Nunha poxa holandesa, inicialmente ofrécese un lote a un prezo superior ao que o comprador está disposto a pagar. O prezo diminúe paso a paso ata que un dos participantes acepta mercar o lote ao prezo actual.

Neste exemplo utilizamos as mesmas constantes que no anterior, así como o paso de prezo cando diminúe o delta. O script da conta verifica se o participante é realmente o primeiro en apostar. En caso contrario, o DataTransaction non é aceptado pola cadea de bloques.

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. Poxa "todo pago"

"All-pay" é unha poxa na que todos os participantes pagan a oferta, independentemente de quen gañe o lote. Cada novo participante paga unha oferta e o participante que faga a oferta máxima gaña o lote.

No noso exemplo, cada participante da poxa fai unha oferta a través de DataTransaction con (clave, valor)* = ("gañador", enderezo), ("prezo", prezo). Esta transacción de datos só se aproba se este participante xa ten unha transacción de transferencia coa súa sinatura e a súa oferta é superior a todas as anteriores. A poxa continúa ata que se alcanza a altura 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)
}

Seguros / Crowdfunding

Consideremos unha situación na que precisa asegurar os activos dos usuarios contra perdas financeiras. Por exemplo, un usuario quere unha garantía de que se un token se deprecia, poderá recuperar o importe total pagado por estes tokens e está disposto a pagar unha cantidade razoable de seguro.

Para implementar isto, é necesario emitir "fichas de seguro". Despois instálase un script na conta do tomador da póliza, que permite executar só aquelas Transaccións de Intercambio que cumpran determinadas condicións.

Para evitar o dobre gasto, cómpre solicitar ao usuario que envíe unha DataTransaction á conta do tomador da póliza de antemán con (key, value) = (purchaseTransactionId, sellOrderId) e prohibir o envío de DataTransactions cunha clave que xa se utilizou.

Polo tanto, as probas do usuario deben conter o ID da transacción da compra do token de seguro. O par de moedas debe ser o mesmo que na transacción de compra. O custo tamén debe ser igual ao fixado no momento da compra, menos o prezo do seguro.

Enténdese que posteriormente a conta do seguro compra fichas de seguro ao usuario a un prezo non inferior ao que os adquiriu: a conta do seguro crea unha Transacción de Intercambio, o usuario asina o pedido (se a transacción se realiza correctamente), o conta de seguro asina o segundo pedido e toda a transacción e envíao á cadea de bloques.

Se non se produce ningunha compra, o usuario pode crear unha Transacción de intercambio segundo as regras descritas no script e enviar a transacción á cadea de bloques. Deste xeito, o usuario pode devolver o diñeiro gastado na compra de fichas aseguradas.

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 de seguro pode converterse nun activo intelixente, por exemplo, para prohibir a súa transferencia a terceiros.

Este esquema tamén se pode implementar para os tokens de crowdfunding, que se devolven aos propietarios se non se recolleu a cantidade necesaria.

Impostos sobre transaccións

Os contratos intelixentes tamén son aplicables nos casos en que sexa necesario recadar impostos por cada transacción con varios tipos de activos. Isto pódese facer a través dun novo activo con instalado patrocinio para transaccións con activos intelixentes:

1. Emitimos FeeCoin, que se enviará aos usuarios a un prezo fixo: 0,01 WAVES = 0,001 FeeCoin.

2. Establece o patrocinio de FeeCoin e o tipo de cambio: 0,001 WAVES = 0,001 FeeCoin.

3. Establece o seguinte script para o activo intelixente:

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
}

Agora, cada vez que alguén transfira N activos intelixentes, darache FeeCoin por un importe de N/taxDivisor (que se pode mercar a ti en 10 *N/taxDivisor WAVES), e daralle ao mineiro N/taxDivisor WAVES. Como resultado, o seu beneficio (imposto) será de 9*N / taxDivisor WAVES.

Tamén pode realizar impostos mediante un script de activos intelixentes 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 programas de fidelización

O cashback é un tipo de programa de fidelización no que o comprador recupera parte do importe gastado nun produto ou servizo.

Ao implementar este caso mediante unha conta intelixente, debemos comprobar as probas do mesmo xeito que o fixemos no caso do seguro. Para evitar o dobre gasto, o usuario debe enviar unha DataTransaction con (clave, valor) = (purchaseTransactionId, cashbackTransactionId) antes de recibir o cashback.

Tamén debemos establecer unha prohibición das claves existentes mediante DataTransaction. cashbackDivisor - unidade dividida pola cota de cashback. Eses. se a cota de reembolso é 0.1, entón 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)
}

Intercambio atómico

O intercambio atómico permite aos usuarios intercambiar activos sen a axuda dun intercambio. Cun intercambio atómico, ambos os participantes na transacción deben confirmalo nun período de tempo determinado.

Se polo menos un dos participantes non proporciona a confirmación correcta da transacción dentro do tempo asignado para a transacción, a transacción cancelarase e o intercambio non se produce.

No noso exemplo, usaremos o seguinte script de conta intelixente:

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
}

No seguinte artigo analizaremos o uso das contas intelixentes en instrumentos financeiros como opcións, futuros e facturas.

Fonte: www.habr.com

Engadir un comentario