Aplicaciones de las cuentas inteligentes Waves: desde subastas hasta programas de bonificación

Aplicaciones de las cuentas inteligentes Waves: desde subastas hasta programas de bonificación

Blockchain a menudo se asocia únicamente con las criptomonedas, pero las áreas de aplicación de la tecnología DLT son mucho más amplias. Una de las áreas más prometedoras para el uso de blockchain es un contrato inteligente que se ejecuta automáticamente y no requiere confianza entre las partes que lo celebran.

RIDE: un lenguaje para contratos inteligentes

Waves ha desarrollado un lenguaje especial para contratos inteligentes: RIDE. Su documentación completa se encuentra aquí. Y aquí - artículo sobre este tema en Habr.

El contrato RIDE es un predicado y devuelve "verdadero" o "falso" como resultado. En consecuencia, la transacción se registra en la cadena de bloques o se rechaza. El contrato inteligente garantiza plenamente el cumplimiento de condiciones específicas. Actualmente no es posible generar transacciones a partir de un contrato en RIDE.

Hoy en día existen dos tipos de contratos inteligentes Waves: cuentas inteligentes y activos inteligentes. Una cuenta inteligente es una cuenta de usuario normal, pero se le configura un script que controla todas las transacciones. Un script de cuenta inteligente podría verse así, por ejemplo:

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

tx es una transacción en proceso y permitimos usar el mecanismo de coincidencia de patrones solo si no es una transacción de transferencia. La coincidencia de patrones en RIDE se utiliza para verificar el tipo de transacción. Todas las cuentas existentes se pueden procesar en el script de cuenta inteligente tipos de transacciones.

El script también puede declarar variables, usar construcciones "if-then-else" y otros métodos para verificar completamente las condiciones. Para garantizar que los contratos tengan una integridad y una complejidad (costo) demostrables que sean fáciles de predecir antes de que comience la ejecución del contrato, RIDE no contiene bucles ni declaraciones de salto.

Otras características de las cuentas Waves incluyen la presencia de un "estado", es decir, el estado de la cuenta. Puede escribir un número infinito de pares (clave, valor) en el estado de la cuenta mediante transacciones de datos (DataTransaction). Luego, esta información se puede procesar tanto a través de la API REST como directamente en el contrato inteligente.

Cada transacción puede contener una serie de pruebas en las que se puede ingresar la firma del participante, el ID de la transacción requerida, etc.

Trabajar con RIDE a través de IDE le permite ver la vista compilada del contrato (si está compilado), crear nuevas cuentas y configurar scripts para él, así como enviar transacciones a través de la línea de comando.

Para un ciclo completo, incluida la creación de una cuenta, la instalación de un contrato inteligente y el envío de transacciones, también puede utilizar una biblioteca para interactuar con la API REST (por ejemplo, C#, C, Java, JavaScript, Python, Rust, Elixir). . Para comenzar a trabajar con el IDE, simplemente haga clic en el botón NUEVO.

Las posibilidades de utilizar contratos inteligentes son amplias: desde prohibir transacciones a determinadas direcciones ("lista negra") hasta dApps complejas.

Ahora veamos ejemplos específicos del uso de contratos inteligentes en los negocios: al realizar subastas, seguros y crear programas de fidelización.

Subastas

Una de las condiciones para el éxito de una subasta es la transparencia: los participantes deben tener confianza en que es imposible manipular las ofertas. Esto se puede lograr gracias a la cadena de bloques, donde los datos inmutables sobre todas las apuestas y el momento en que se realizaron estarán disponibles para todos los participantes.

En la cadena de bloques Waves, las ofertas se pueden registrar en el estado de la cuenta de subasta a través de DataTransaction.

También puede establecer la hora de inicio y finalización de la subasta utilizando números de bloque: la frecuencia de generación de bloques en la cadena de bloques Waves es aproximadamente igual a 60 segundos.

1. Subasta inglesa de precios ascendentes

Los participantes en una subasta inglesa hacen ofertas en competencia entre sí. Cada nueva apuesta debe superar a la anterior. La subasta finaliza cuando no hay más postores que superen la última oferta. En este caso, el mejor postor deberá aportar el importe indicado.

También existe la opción de subasta en la que el vendedor fija un precio mínimo para el lote, y el precio final debe superarlo. De lo contrario, el lote queda sin vender.

En este ejemplo, estamos trabajando con una cuenta creada específicamente para la subasta. La duración de la subasta es de 3000 bloques y el precio inicial del lote es 0,001 WAVES. Un participante puede realizar una oferta enviando una DataTransaction con la clave "precio" y el valor de su oferta.

El precio de la nueva oferta debe ser mayor que el precio actual de esta clave y el participante debe tener al menos [nueva_oferta + comisión] tokens en su cuenta. La dirección del postor debe registrarse en el campo "remitente" en DataTransaction y la altura del bloque de oferta actual debe estar dentro del período de la subasta.

Si al final de la subasta el participante ha fijado el precio más alto, puede enviar una ExchangeTransaction para pagar el lote correspondiente al precio y 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. Subasta holandesa de precios a la baja

En una subasta holandesa, inicialmente se ofrece un lote a un precio superior al que el comprador está dispuesto a pagar. El precio se reduce paso a paso hasta que uno de los participantes acepta comprar el lote al precio actual.

En este ejemplo utilizamos las mismas constantes que en el anterior, así como el paso del precio cuando delta disminuye. El script de la cuenta comprueba si el participante es realmente el primero en realizar una apuesta. De lo contrario, la cadena de bloques no acepta la transacción de datos.

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. Subasta “pago total”

“Todo pago” es una subasta en la que todos los participantes pagan la oferta, independientemente de quién gane el lote. Cada nuevo participante paga una oferta y el participante que hace la oferta máxima gana el lote.

En nuestro ejemplo, cada participante de la subasta realiza una oferta a través de DataTransaction con (clave, valor)* = (“ganador”, dirección), (“precio”, precio). Dicha DataTransaction se aprueba sólo si este participante ya tiene una TransferTransaction con su firma y su oferta es mayor que todas las anteriores. La subasta continúa hasta que se alcanza 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)
}

Seguros / Crowdfunding

Consideremos una situación en la que es necesario asegurar los activos de los usuarios contra pérdidas financieras. Por ejemplo, un usuario quiere una garantía de que si un token se deprecia, podrá recuperar el monto total pagado por esos tokens y está dispuesto a pagar una cantidad razonable de seguro.

Para implementar esto, es necesario emitir “fichas de seguro”. Luego se instala un script en la cuenta del titular de la póliza, que permite ejecutar solo aquellas ExchangeTransactions que cumplan ciertas condiciones.

Para evitar el doble gasto, debe solicitar al usuario que envíe una DataTransaction a la cuenta del titular de la póliza por adelantado con (clave, valor) = (purchaseTransactionId, sellOrderId) y prohibir el envío de DataTransactions con una clave que ya se ha utilizado.

Por lo tanto, los comprobantes del usuario deben contener el ID de la transacción de compra del token de seguro. El par de divisas debe ser el mismo que en la transacción de compra. El coste también debe ser igual al fijado en el momento de la compra, menos el precio del seguro.

Se entiende que posteriormente la cuenta del seguro compra tokens de seguro al usuario a un precio no inferior a aquel al que los compró: la cuenta del seguro crea una ExchangeTransaction, el usuario firma la orden (si la transacción se completa correctamente), el La cuenta de seguro firma el segundo pedido y la transacción completa y la envía a la cadena de bloques.

Si no se produce ninguna compra, el usuario puede crear una ExchangeTransaction de acuerdo con las reglas descritas en el script y enviar la transacción a la cadena de bloques. De esta forma el usuario podrá devolver el dinero gastado en la compra de tokens asegurados.

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 puede convertirse en un activo inteligente, por ejemplo, para prohibir su transferencia a terceros.

Este esquema también se puede implementar para tokens de crowdfunding, que se devuelven a sus propietarios si no se ha recaudado la cantidad requerida.

Impuestos a las transacciones

Los contratos inteligentes también son aplicables en los casos en los que es necesario recaudar impuestos por cada transacción con varios tipos de activos. Esto se puede hacer a través de un nuevo activo con instalado patrocinio para transacciones con activos inteligentes:

1. Emitimos FeeCoin, que se enviará a los usuarios a un precio fijo: 0,01 WAVES = 0,001 FeeCoin.

2. Establezca el patrocinio de FeeCoin y el tipo de cambio: 0,001 WAVES = 0,001 FeeCoin.

3. Configure el siguiente script para el activo inteligente:

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
}

Ahora, cada vez que alguien transfiera N activos inteligentes, le dará FeeCoin por la cantidad de N/taxDivisor (que puede comprarle a 10 *N/taxDivisor WAVES), y usted le dará al minero N/taxDivisor WAVES. Como resultado, su beneficio (impuestos) será 9*N / taxDivisor WAVES.

También puede realizar impuestos utilizando un script de activos inteligente y 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
}

Programas de cashback y fidelización

El cashback es un tipo de programa de fidelización en el que el comprador recupera parte del importe gastado en un producto o servicio.

Al implementar este caso utilizando una cuenta inteligente, debemos verificar las pruebas de la misma manera que lo hicimos en el caso del seguro. Para evitar el doble gasto, el usuario debe enviar una DataTransaction con (clave, valor) = (purchaseTransactionId, cashbackTransactionId) antes de recibir el reembolso.

También debemos establecer una prohibición sobre las claves existentes que utilizan DataTransaction. cashbackDivisor: unidad dividida por la parte del cashback. Aquellos. si la proporción de reembolso es 0.1, entonces 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

El intercambio atómico permite a los usuarios intercambiar activos sin la ayuda de un intercambio. Con un intercambio atómico, ambos participantes en la transacción deben confirmarla dentro de un cierto período de tiempo.

Si al menos uno de los participantes no proporciona la confirmación correcta de la transacción dentro del tiempo asignado para la transacción, la transacción se cancela y el intercambio no se produce.

En nuestro ejemplo, usaremos el siguiente script de cuenta inteligente:

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 próximo artículo veremos el uso de cuentas inteligentes en instrumentos financieros como opciones, futuros y letras.

Fuente: habr.com

Añadir un comentario