Blockchain a menudo se asocia solo con criptomonedas, pero el alcance de la tecnología DLT es mucho más amplio. Una de las áreas más prometedoras para usar blockchain es un contrato inteligente que se ejecuta automáticamente y no requiere confianza entre las partes que lo concluyeron.
RIDE - lenguaje para contratos inteligentesWaves ha desarrollado un lenguaje especial para contratos inteligentes: RIDE. Su documentación completa está
aquí . Y aquí,
un artículo sobre este tema sobre Habré.
El contrato en RIDE es un predicado y devuelve "verdadero" o "falso" en la salida. En consecuencia, una transacción se escribe en la cadena de bloques o se rechaza. Un contrato inteligente garantiza plenamente el cumplimiento de las condiciones especificadas. La generación de transacciones a partir de un contrato en RIDE actualmente no es posible.
Hoy en día, hay dos tipos de contratos inteligentes de Waves: cuentas inteligentes y activos inteligentes. Una cuenta inteligente es una cuenta de usuario normal, pero se configura un script que controla todas las transacciones. Un script de cuenta inteligente podría verse así:
match tx { case t: TransferTransaction | MassTransferTransaction => false case _ => true }
tx es una transacción procesada que permitimos usar el mecanismo de coincidencia de patrones solo si no es una transacción de transferencia. La coincidencia de patrones RIDE se utiliza para verificar el tipo de transacción. En el script de la cuenta inteligente, se pueden procesar todos los
tipos de transacciones existentes.
Además, se pueden declarar variables en el script, se pueden utilizar construcciones "if-then-else" y otros métodos para la verificación completa de las condiciones. Para que los contratos tengan finalización comprobable y complejidad (costo), que es fácil de predecir antes del inicio del contrato, RIDE no contiene bucles y operadores como jump.
Entre otras características de las cuentas de Waves está la presencia de un "estado", es decir, el estado de la cuenta. Se puede escribir un número infinito de pares (clave, valor) en el estado de la cuenta mediante transacciones de datos (DataTransaction). Además, 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 puede ingresar la firma del participante, la identificación de la transacción necesaria, etc.
Trabajar con RIDE a través del
IDE le permite ver la forma compilada del contrato (si está compilado), crear nuevas cuentas y establecer 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 usar la 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 usar contratos inteligentes son amplias: desde la prohibición de transacciones a ciertas direcciones (la "lista negra") hasta dApps complejas.
Ahora veamos ejemplos específicos del uso de contratos inteligentes en los negocios: durante las subastas, los seguros y la creación de programas de lealtad.
SubastasUna de las condiciones para una subasta exitosa es la transparencia: los licitantes deben asegurarse de que sea imposible manipular las ofertas. Esto se puede lograr gracias a blockchain, donde los datos sin cambios en 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 la 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 de aproximadamente
60 segundos.
1. Subasta de precios en alza en inglésParticipantes en la subasta en inglés, compitiendo entre sí. Cada nueva apuesta debe exceder la anterior. La subasta finaliza cuando ya no hay más ganas de superar la última oferta. En este caso, el mejor postor debe proporcionar la cantidad declarada.
También hay una opción de subasta en la que el vendedor establece el precio mínimo para el lote, y el precio final debe excederlo. De lo contrario, el lote permanece sin vender.
En este ejemplo, estamos trabajando con una cuenta especialmente creada para la subasta. La duración de la subasta es de 3000 bloques, y el precio inicial del lote es de 0.001 ONDAS. Un participante puede hacer una apuesta enviando una DataTransaction con la clave de "precio" y el valor de su oferta, en las pruebas de transacción, debe agregar la clave pública y la firma del remitente.
El precio de la nueva apuesta debe ser más alto que el precio actual de esta clave, y el participante debe tener al menos [new_state + comisión] tokens en la cuenta. La dirección del postor debe ingresarse en el campo "remitente" en DataTransaction, y la altura actual del bloque de la oferta debe estar dentro del período de la subasta.
Si al final de la subasta el postor ha establecido el precio más alto, puede enviar ExchangeTransaction para pagar el lote correspondiente al precio indicado y al par de divisas.
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 pk = d.proofs[1] let address = addressFromPublicKey(pk) let priceIsBigger = newPrice > currentPrice let fee = 700000 let hasMoney = wavesBalance(address) + fee >= newPrice let correctFields = size(d.data) == 2 && extract(getString(d.data, "sender")) == toBase58String(address.bytes) startHeight <= height && height <= finishHeight && priceIsBigger && hasMoney && correctFields && sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1]) case o : Order => # let pk = o.proofs[1] let address = addressFromPublicKey(pk) let senderIsWinner = address == addressFromString(extract(getString(this, "sender"))) #, , let correctAssetPair = o.assetPair.amountAsset == token && ! isDefined(o.assetPair.priceAsset) let correctAmount = o.amount == 1 let correctPrice = o.price == extract(getInteger(this, "price")) height > finishHeight && senderIsWinner && correctAssetPair && correctAmount && correctPrice && sigVerify(o.bodyBytes, o.proofs[0], o.proofs[1]) case _ => false }
2. Subasta holandesa de precios a la bajaEn 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 la anterior, así como el paso de precio al bajar delta. El guión de la cuenta comprueba si el participante es realmente el primero en apostar. En las pruebas de transacción, debe agregar la clave pública y la firma del remitente. De lo contrario, la cadena de bloques no acepta DataTransaction.
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")) # let pk = d.proofs[1] let address = addressFromPublicKey(pk) let correctFields = extract(getString(d.data, "sender")) == toBase58String(address.bytes) && size(d.data) == 2 && newPrice == currentPrice #, "sender" let noBetsBefore = !isDefined(getInteger(this, "sender")) let fee = 700000 let hasMoney = wavesBalance(address) - fee >= newPrice startHeight <= height && height <= finishHeight && noBetsBefore && hasMoney && correctFields && sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1]) case o : Order => # let pk = o.proofs[1] let address = addressFromPublicKey(pk) #, sender let senderIsWinner = address == addressFromString(extract(getString(this, "sender"))) #, mount , - - waves let correctAssetPair = o.assetPair.amountAsset == token && ! isDefined(o.assetPair.priceAsset) let correctAmount = o.amount == 1 let correctPrice = o.price == extract(getInteger(this, "price")) height > finishHeight && senderIsWinner && correctAssetPair && correctAmount && correctPrice && sigVerify(o.bodyBytes, o.proofs[0], o.proofs[1]) case _ => false }
3. Subasta de pago total"Todo pago": una subasta, en la que todos los participantes pagan la oferta, pagan, independientemente de quién gane el lote. Cada nuevo participante paga la apuesta, y el participante que ha realizado la apuesta máxima gana el lote.
En nuestro ejemplo, cada participante de la subasta hace una oferta a través de DataTransaction con (clave, valor) * = ("ganador", dirección), ("precio", precio). Dicha DataTransaction se aprueba solo si para este participante ya existe una TransferTransaction con su firma y su tasa es más alta que todas las anteriores. La subasta continúa hasta llegar al final.
let startHeight = 1000 let endHeight = 2000 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 / CrowdfundingConsidere una situación en la que necesita asegurar los activos de los usuarios de pérdidas financieras. Por ejemplo, el usuario desea obtener una garantía de que si el token se deprecia, podrá devolver el monto total pagado por estos tokens y está listo para pagar una cantidad razonable de seguro.
Para implementar esto, debe emitir "tokens de seguro". Luego, se instala un script en la cuenta del titular de la póliza que le permite ejecutar solo aquellas ExchangeTransactions que cumplan ciertas condiciones.
Para evitar el doble gasto, debe solicitar al usuario que envíe DataTransaction por adelantado a la cuenta del titular de la póliza con (key, value) = (adquirirTransactionId, sellOrderId) y prohíba enviar DataTransactions con la clave ya utilizada.
Por lo tanto, las pruebas de usuario deben contener el ID de transacción de la compra del token de seguro. El par de divisas debe ser el mismo que en la transacción de compra. El costo también debe ser igual al registrado al momento de la compra, menos el precio del seguro.
Se entiende que posteriormente la cuenta de seguro canjea las fichas de seguro del usuario a un precio no inferior al precio al que las compró: la cuenta de seguro crea ExchangeTransaction, el usuario firma el pedido (si la transacción se completa correctamente), la cuenta de seguro firma el segundo pedido y la transacción completa y lo envía a blockchain .
Si la compra no ocurre, el usuario puede crear un Pedido de acuerdo con las reglas descritas en el script y enviar la transacción a la cadena de bloques. Para que el usuario pueda 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 o : Order => # , if !isDefined(o.proofs[7]) then sigVerify(o.bodyBytes, o.proofs[0], o.senderPublicKey) else # , let purchaseTx = transactionById(o.proofs[7]) let purchaseTxHeight = extract(transactionHeightById(o.proofs[7])) # match purchaseTx { case purchase : ExchangeTransaction => let correctSender = purchase.sender == o.sender let correctAssetPair = o.assetPair.amountAsset == insuranceToken && purchase.sellOrder.assetPair.amountAsset == insuranceToken && o.assetPair.priceAsset == purchase.sellOrder.assetPair.priceAsset let correctPrice = o.price == purchase.price - insurancePrice && o.amount == purchase.amount let correctHeight = height > purchaseTxHeight + freezePeriod #, - ID let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == o.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 los propietarios si no se ha recaudado la cantidad necesaria.
Impuestos de transacciónLos contratos inteligentes también son aplicables en los casos en que es necesario recaudar impuestos de cada transacción con varios tipos de activos. Esto se puede hacer a través de un nuevo activo
patrocinado para transacciones con activos inteligentes:
1. Lanzamos FeeCoin, que se enviará a los usuarios a un precio fijo: 0.01 WAVES = 0.001 FeeCoin.
2. Establecimos patrocinio para FeeCoin y el tipo de cambio: 0.001 WAVES = 0.001 FeeCoin.
3. Configuramos 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 transfiere N activos inteligentes, le dará a FeeCoin la cantidad de N / taxDivisor (que puede comprarle por 10 * N / taxDivisor WAVES), y usted le dará al minero N / taxDivisor WAVES. Como resultado, su beneficio (impuesto) será de 9 * N / taxDivisor WAVES.
También puede gravar usando un script de activos inteligentes 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 }
Cashback y programas de fidelizaciónEl reembolso es un tipo de programa de fidelización en el que una parte de la cantidad gastada en un producto o servicio se devuelve al comprador.
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, antes de recibir un reembolso, el usuario debe enviar una DataTransaction con (clave, valor) = (buyTransactionId, cashbackTransactionId).
También debemos prohibir las claves existentes utilizando una DataTransaction. cashbackDivisor: una unidad dividida por la parte de cashback. Es decir si el porcentaje de devolución de efectivo es 0.1, entonces el divisor de devolución de efectivo 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ómicoEl intercambio atómico permite a los usuarios intercambiar activos sin la ayuda de un intercambio. En un intercambio atómico, se requiere que ambos participantes en la transacción lo confirmen 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 no se realiza ningún intercambio.
En nuestro ejemplo, utilizaremos 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, consideraremos el uso de cuentas inteligentes en instrumentos financieros, como opciones, futuros y facturas.