Aplicação de contas inteligentes Waves: de leilões a programas de bônus

Aplicação de contas inteligentes Waves: de leilões a programas de bônus

O Blockchain é frequentemente associado apenas a criptomoedas, mas as áreas de aplicação da tecnologia DLT são muito mais amplas. Uma das áreas mais promissoras para o uso do blockchain é um contrato inteligente que é executado automaticamente e não requer confiança entre as partes que o celebraram.

RIDE – uma linguagem para contratos inteligentes

A Waves desenvolveu uma linguagem especial para contratos inteligentes – RIDE. Sua documentação completa está localizada aqui. E aqui - artigo sobre este tema em Habr.

O contrato RIDE é um predicado e retorna “true” ou “false” como saída. Conseqüentemente, a transação é registrada no blockchain ou rejeitada. O contrato inteligente garante totalmente o cumprimento das condições especificadas. Atualmente não é possível gerar transações a partir de um contrato no RIDE.

Hoje existem dois tipos de contratos inteligentes Waves: contas inteligentes e ativos inteligentes. Uma conta inteligente é uma conta de usuário normal, mas é definido um script que controla todas as transações. Um script de conta inteligente pode ter esta aparência, por exemplo:

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

tx é uma transação sendo processada que permitimos usar o mecanismo de correspondência de padrões somente se não for uma transação de transferência. A correspondência de padrões no RIDE é usada para verificar o tipo de transação. Todas as contas existentes podem ser processadas no script de conta inteligente tipos de transação.

O script também pode declarar variáveis, usar construções “if-then-else” e outros métodos para verificar totalmente as condições. Para garantir que os contratos tenham integridade e complexidade (custo) comprováveis ​​e fáceis de prever antes do início da execução do contrato, o RIDE não contém loops ou instruções de salto.

Outras características das contas Waves incluem a presença de um “estado”, ou seja, o estado da conta. Você pode gravar um número infinito de pares (chave, valor) no estado da conta usando transações de dados (DataTransaction). Essas informações podem então ser processadas por meio da API REST e diretamente no contrato inteligente.

Cada transação pode conter uma série de provas, nas quais podem ser inseridas a assinatura do participante, o ID da transação necessária, etc.

Trabalhando com RIDE via IDE permite ver a visão compilada do contrato (se for compilado), criar novas contas e definir scripts para ele, bem como enviar transações via linha de comando.

Para um ciclo completo, incluindo criação de uma conta, instalação de um contrato inteligente nela e envio de transações, você também pode usar uma biblioteca para interagir com a API REST (por exemplo, C#, C, Java, JavaScript, Python, Rust, Elixir) . Para começar a trabalhar com o IDE, basta clicar no botão NOVO.

As possibilidades de utilização de contratos inteligentes são amplas: desde a proibição de transações para determinados endereços (“lista negra”) até dApps complexos.

Agora vejamos exemplos específicos do uso de contratos inteligentes nos negócios: na realização de leilões, seguros e na criação de programas de fidelidade.

Leilões

Uma das condições para um leilão bem-sucedido é a transparência: os participantes devem ter certeza de que é impossível manipular os lances. Isto pode ser conseguido graças ao blockchain, onde dados imutáveis ​​sobre todas as apostas e o momento em que foram feitas estarão disponíveis para todos os participantes.

Na blockchain Waves, os lances podem ser registrados no estado da conta de leilão via DataTransaction.

Você também pode definir o horário de início e término do leilão usando números de bloco: a frequência de geração de bloco no blockchain Waves é aproximadamente igual a 60 segundos.

1. Leilão de preços ascendentes em inglês

Os participantes de um leilão inglês fazem lances competindo entre si. Cada nova aposta deve exceder a anterior. O leilão termina quando não houver mais licitantes que excedam o último lance. Neste caso, o licitante com lance mais alto deverá fornecer o valor declarado.

Existe também a opção de leilão em que o vendedor define um preço mínimo para o lote, devendo o preço final ultrapassá-lo. Caso contrário, o lote não será vendido.

Neste exemplo, estamos trabalhando com uma conta criada especificamente para o leilão. A duração do leilão é de 3000 blocos e o preço inicial do lote é de 0,001 WAVES. Um participante pode fazer um lance enviando uma DataTransaction com a chave “preço” e o valor do seu lance.

O preço do novo lance deve ser superior ao preço atual desta chave, e o participante deve ter pelo menos [novo_bid + comissão] tokens em sua conta. O endereço do licitante deverá ser cadastrado no campo “remetente” do DataTransaction, e a altura do bloco de lances atual deverá estar dentro do período do leilão.

Se ao final do leilão o participante tiver definido o preço mais alto, ele poderá enviar uma ExchangeTransaction para pagar o lote correspondente ao preço e par de moedas 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. Leilão holandês de preços decrescentes

Num leilão holandês, um lote é inicialmente oferecido a um preço superior ao que o comprador está disposto a pagar. O preço é reduzido gradativamente até que um dos participantes concorde em comprar o lote pelo preço atual.

Neste exemplo usamos as mesmas constantes do anterior, bem como a variação do preço quando o delta diminui. O script da conta verifica se o participante é realmente o primeiro a fazer uma aposta. Caso contrário, a DataTransaction não será aceita pela 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. Leilão “tudo pago”

“All-pay” é um leilão em que todos os participantes pagam o lance, independentemente de quem ganha o lote. Cada novo participante paga um lance, e o participante que fizer o lance máximo ganha o lote.

No nosso exemplo, cada participante do leilão faz um lance via DataTransaction com (chave, valor)* = (“vencedor”, endereço),(“preço”, preço). Tal DataTransaction é aprovada somente se este participante já possuir uma TransferTransaction com sua assinatura e seu lance for superior a todos os anteriores. O leilão continua até que endHeight seja alcançado.

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/financiamento coletivo

Vamos considerar uma situação em que você precisa segurar os ativos dos usuários contra perdas financeiras. Por exemplo, um usuário deseja uma garantia de que, se um token se desvalorizar, ele poderá receber de volta o valor total pago por esses tokens e está disposto a pagar um valor razoável de seguro.

Para implementar isso, é necessário emitir “tokens de seguro”. Em seguida, um script é instalado na conta do segurado, permitindo que apenas as ExchangeTransactions que atendam a determinadas condições sejam executadas.

Para evitar gastos duplos, é necessário solicitar antecipadamente ao usuário o envio de DataTransaction para a conta do segurado com (chave, valor) = (purchaseTransactionId, sellOrderId) e proibir o envio de DataTransactions com chave já utilizada.

Portanto, os comprovantes do usuário deverão conter o ID da transação de compra do token do seguro. O par de moedas deve ser o mesmo da transação de compra. O custo também deve ser igual ao fixado no momento da compra, menos o preço do seguro.

Entende-se que posteriormente a conta de seguro compra tokens de seguro do usuário a um preço não inferior àquele pelo qual ele os comprou: a conta de seguro cria uma ExchangeTransaction, o usuário assina o pedido (se a transação for concluída corretamente), o a conta de seguro assina o segundo pedido e toda a transação e envia para o blockchain.

Caso não ocorra nenhuma compra, o usuário pode criar uma ExchangeTransaction de acordo com as regras descritas no script e enviar a transação para o blockchain. Desta forma o usuário pode devolver o dinheiro gasto na compra dos tokens segurados.

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

Um token de seguro pode se tornar um ativo inteligente, por exemplo, para proibir sua transferência a terceiros.

Este esquema também pode ser implementado para tokens de crowdfunding, que são devolvidos aos proprietários caso a quantia exigida não tenha sido coletada.

Impostos sobre transações

Os contratos inteligentes também são aplicáveis ​​nos casos em que é necessária a cobrança de imposto sobre cada transação com diversos tipos de ativos. Isto pode ser feito através de um novo ativo com instalação patrocínio para transações com ativos inteligentes:

1. Emitimos FeeCoin, que será enviado aos usuários a um preço fixo: 0,01 WAVES = 0,001 FeeCoin.

2. Defina o patrocínio para FeeCoin e taxa de câmbio: 0,001 WAVES = 0,001 FeeCoin.

3. Defina o seguinte script para o ativo 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
}

Agora, toda vez que alguém transferir N ativos inteligentes, ele lhe dará FeeCoin no valor de N/taxDivisor (que pode ser comprado de você por 10 *N/taxDivisor WAVES), e você dará ao minerador N/taxDivisor WAVES. Como resultado, seu lucro (imposto) será de 9*N / taxDivisor WAVES.

Você também pode realizar tributação usando um script de ativo inteligente 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
}

Programas de cashback e fidelidade

Cashback é uma espécie de programa de fidelidade em que o comprador recebe de volta parte do valor gasto em um produto ou serviço.

Ao implementar este caso através de uma conta inteligente, devemos verificar as provas da mesma forma que fizemos no caso do seguro. Para evitar gastos duplos, o usuário deve enviar uma DataTransaction com (chave, valor) = (purchaseTransactionId, cashbackTransactionId) antes de receber o cashback.

Devemos também proibir as chaves existentes usando DataTransaction. cashbackDivisor - unidade dividida pela cota de cashback. Aqueles. se a parcela de cashback for 0.1, então 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)
}

Troca atômica

A troca atômica permite que os usuários troquem ativos sem a ajuda de uma troca. Com uma troca atômica, ambos os participantes da transação são obrigados a confirmá-la dentro de um determinado período de tempo.

Se pelo menos um dos participantes não fornecer a confirmação correta da transação dentro do prazo previsto para a transação, a transação é cancelada e a troca não ocorre.

Em nosso exemplo, usaremos o seguinte script de conta 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
}

No próximo artigo veremos o uso de contas inteligentes em instrumentos financeiros como opções, futuros e letras.

Fonte: habr.com

Adicionar um comentário