Usando contas inteligentes Waves: de leilões a programas de bônus

imagem

O Blockchain geralmente é associado apenas a criptomoedas, mas o escopo da tecnologia DLT é muito mais amplo. Uma das áreas mais promissoras para o uso da blockchain é um contrato inteligente que é executado automaticamente e não requer confiança entre as partes que o concluíram.

RIDE - linguagem para contratos inteligentes

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

O contrato no RIDE é um predicado e retorna "verdadeiro" ou "falso" na saída. Por conseguinte, uma transação é gravada na blockchain ou rejeitada. Um contrato inteligente garante totalmente o cumprimento de condições especificadas. Atualmente, a geração de transações a partir de um contrato no RIDE não é possível.

Hoje, existem dois tipos de contratos inteligentes da Waves: contas inteligentes e ativos inteligentes. Uma conta inteligente é uma conta de usuário comum, mas é definido um script que controla todas as transações. Um script de conta inteligente pode ser assim:

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

tx é uma transação processada que permitimos usar o mecanismo de correspondência de padrões apenas se não for uma transação de transferência. A correspondência de padrões RIDE é usada para verificar o tipo de transação. No script de conta inteligente, todos os tipos de transação existentes podem ser processados.

Além disso, variáveis ​​podem ser declaradas no script, construções "if-then-else" e outros métodos para a verificação completa das condições podem ser usados. Para que os contratos tenham conclusão e complexidade (custo) comprováveis, fáceis de prever antes do início do contrato, o RIDE não contém loops e operadores como jump.

Entre outros recursos das contas Waves está a presença de um "estado", isto é, o estado da conta. Um número infinito de pares (chave, valor) pode ser gravado no estado da conta usando transações de dados (DataTransaction). Além disso, essas informações podem ser processadas por meio da API REST e diretamente no contrato inteligente.

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

Trabalhar com o RIDE por meio do IDE permite visualizar a forma compilada do contrato (se ele for compilado), criar novas contas e definir scripts para ele, além de enviar transações pela linha de comando.

Por um ciclo completo, incluindo a criação de uma conta, a instalação de um contrato inteligente e o envio de transações, você também pode usar a 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 uso de contratos inteligentes são amplas: da proibição de transações a determinados endereços (a "lista negra") aos dApps complexos.

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

Leilões

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

Na blockchain Waves, os lances podem ser registrados no estado da conta do 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 é de aproximadamente 60 segundos.

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

Participantes no leilão em inglês, concorrendo entre si. Cada nova aposta deve exceder a anterior. O leilão termina quando não houver mais disposição para exceder o último lance. Nesse caso, o maior lance deve fornecer o valor declarado.

Há também uma opção de leilão na qual o vendedor define o preço mínimo para o lote e o preço final deve exceder. Caso contrário, o lote não será vendido.

Neste exemplo, estamos trabalhando com uma conta criada especialmente 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 uma aposta enviando uma DataTransaction com a chave "price" e o valor de sua oferta; nas provas de transação, você precisa adicionar a chave pública e a assinatura do remetente.

O preço da nova aposta deve ser maior que o preço atual dessa chave, e o participante deve ter pelo menos [novo_estado + comissão] tokens na conta. O endereço do licitante deve ser inserido no campo "remetente" no DataTransaction, e a altura atual do bloco de lances deve estar dentro do período do leilão.

Se, no final do leilão, o licitante tiver definido o preço mais alto, ele poderá enviar ExchangeTransaction para pagar o lote correspondente no preço indicado e no par de moedas.

 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. Leilão holandês de queda de preços

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

Neste exemplo, usamos as mesmas constantes que a anterior, bem como a etapa de preço ao diminuir o delta. O script da conta verifica se o participante é realmente o primeiro a apostar. Nas provas de transação, você precisa adicionar a chave pública e a assinatura do remetente, caso contrário, DataTransaction não será aceito pelo 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")) #       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. Leilão pago

"Tudo pago" - um leilão, cujos participantes pagam a oferta, pagam, independentemente de quem ganha o lote. Cada novo participante paga a aposta e o participante que fez a aposta máxima ganha o lote.

No nosso exemplo, cada participante do leilão faz um lance por meio de uma DataTransaction com (chave, valor) * = ("vencedor", endereço), ("preço", preço). Essa DataTransaction é aprovada apenas se, para esse participante, já houver uma TransferTransaction com sua assinatura e sua taxa for maior que todas as anteriores. O leilão continua até o fim da altura.

 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 / Crowdfunding

Considere uma situação em que você precisa garantir ativos do usuário contra perdas financeiras. Por exemplo, o usuário deseja obter uma garantia de que, se o token se depreciar, ele poderá devolver o valor total pago por esses tokens e estará pronto para pagar uma quantidade razoável de seguro.

Para implementar isso, você precisa emitir "tokens de seguro". Em seguida, um script é instalado na conta do tomador do seguro que permite executar apenas as ExchangeTransactions que atendem a determinadas condições.

Para evitar gastos duplos, você precisa solicitar que o usuário envie DataTransaction antecipadamente para a conta do segurado com (chave, valor) = (purchaseTransactionId, sellOrderId) e proíba o envio de DataTransactions com a chave já usada.

Portanto, as provas do usuário devem conter o ID da transação da compra do token de seguro. O par de moedas deve ser o mesmo que na transação de compra. O custo também deve ser igual ao registrado no momento da compra, menos o preço do seguro.

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

Se a compra não ocorrer, o usuário poderá criar um Pedido de acordo com as regras descritas no script e enviar a transação para a blockchain. Assim, o usuário pode devolver o dinheiro gasto na compra de 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 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) } 

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

Esse esquema também pode ser implementado para tokens de crowdfunding, que são devolvidos aos proprietários se a quantia necessária não tiver sido coletada.

Impostos sobre transações

Os contratos inteligentes também são aplicáveis ​​nos casos em que é necessário cobrar impostos de cada transação com vários tipos de ativos. Isso pode ser feito por meio de um novo ativo patrocinado para transações com ativos inteligentes:

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

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

3. Definimos 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 fornecerá o FeeCoin no valor de N / taxDivisor (que pode ser comprado por 10 * N / taxDivisor WAVES) e o mineiro N / taxDivisor WAVES. Como resultado, seu lucro (imposto) será 9 * N / taxDivisor WAVES.

Você também pode tributar 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

O reembolso é um tipo de programa de fidelidade no qual uma parte do valor gasto em um produto ou serviço é devolvida ao comprador.

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

Também devemos proibir chaves existentes usando uma DataTransaction. cashbackDivisor - uma unidade dividida pela ação do cashback. I.e. se o compartilhamento de reembolso for de 0,1, 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. Em uma troca atômica, os dois participantes da transação devem confirmar 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 tempo alocado para a transação, a transação será cancelada e nenhuma troca ocorrerá.

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, consideraremos o uso de contas inteligentes em instrumentos financeiros - como opções, futuros e faturas.

Source: https://habr.com/ru/post/pt442238/


All Articles