Application des comptes intelligents Waves : des enchères aux programmes de bonus

Application des comptes intelligents Waves : des enchères aux programmes de bonus

La blockchain est souvent associée uniquement aux crypto-monnaies, mais les domaines d'application de la technologie DLT sont beaucoup plus larges. L'un des domaines les plus prometteurs pour l'utilisation de la blockchain est un contrat intelligent qui est exécuté automatiquement et ne nécessite pas de confiance entre les parties qui l'ont conclu.

RIDE – un langage pour les contrats intelligents

Waves a développé un langage spécial pour les contrats intelligents – RIDE. Sa documentation complète se trouve ici. Et ici - article sur ce sujet sur Habr.

Le contrat RIDE est un prédicat et renvoie « vrai » ou « faux » en sortie. En conséquence, la transaction est soit enregistrée dans la blockchain, soit rejetée. Le contrat intelligent garantit pleinement le respect des conditions spécifiées. Générer des transactions à partir d'un contrat dans RIDE n'est actuellement pas possible.

Il existe aujourd’hui deux types de contrats intelligents Waves : les comptes intelligents et les actifs intelligents. Un compte intelligent est un compte d'utilisateur ordinaire, mais un script est défini pour contrôler toutes les transactions. Un script de compte intelligent pourrait ressembler à ceci, par exemple :

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

tx est une transaction en cours de traitement que nous autorisons à utiliser le mécanisme de correspondance de modèles uniquement s'il ne s'agit pas d'une transaction de transfert. La correspondance de modèles dans RIDE est utilisée pour vérifier le type de transaction. Tous les comptes existants peuvent être traités dans le script de compte intelligent types de transactions.

Le script peut également déclarer des variables, utiliser des constructions « if-then-else » et d'autres méthodes pour vérifier complètement les conditions. Pour garantir que les contrats ont une exhaustivité et une complexité (coût) prouvables faciles à prévoir avant le début de l'exécution du contrat, RIDE ne contient pas de boucles ni d'instructions de saut.

D'autres caractéristiques des comptes Waves incluent la présence d'un « état », c'est-à-dire l'état du compte. Vous pouvez écrire un nombre infini de paires (clé, valeur) dans l'état du compte à l'aide de transactions de données (DataTransaction). Ces informations peuvent ensuite être traitées à la fois via l'API REST et directement dans le contrat intelligent.

Chaque transaction peut contenir un ensemble de preuves dans lesquelles peuvent être inscrits la signature du participant, l'identifiant de la transaction souhaitée, etc.

Travailler avec RIDE via IDE vous permet de voir la vue compilée du contrat (s'il est compilé), de créer de nouveaux comptes et de définir des scripts pour celui-ci, ainsi que d'envoyer des transactions via la ligne de commande.

Pour un cycle complet, comprenant la création d'un compte, l'installation d'un contrat intelligent dessus et l'envoi de transactions, vous pouvez également utiliser une bibliothèque pour interagir avec l'API REST (par exemple, C#, C, Java, JavaScript, Python, Rust, Elixir) . Pour commencer à travailler avec l'EDI, cliquez simplement sur le bouton NOUVEAU.

Les possibilités d'utilisation des contrats intelligents sont larges : de l'interdiction des transactions vers certaines adresses (« liste noire ») aux dApps complexes.

Examinons maintenant des exemples spécifiques d'utilisation de contrats intelligents en entreprise : lors de la conduite d'enchères, d'assurances et de la création de programmes de fidélité.

Les enchères

L'une des conditions d'une vente aux enchères réussie est la transparence : les participants doivent être convaincus qu'il est impossible de manipuler les offres. Ceci peut être réalisé grâce à la blockchain, où des données immuables sur tous les paris et l'heure à laquelle ils ont été effectués seront disponibles pour tous les participants.

Sur la blockchain Waves, les offres peuvent être enregistrées dans l'état du compte d'enchères via DataTransaction.

Vous pouvez également définir l'heure de début et de fin de l'enchère à l'aide de numéros de bloc : la fréquence de génération de blocs dans la blockchain Waves est approximativement égale à 60 secondes.

1. Enchères à prix croissant en anglais

Les participants à une vente aux enchères anglaise placent leurs offres en concurrence les uns avec les autres. Chaque nouvelle mise doit dépasser la précédente. L'enchère se termine lorsqu'il n'y a plus d'enchérisseurs pour dépasser la dernière enchère. Dans ce cas, le plus offrant devra fournir le montant indiqué.

Il existe également une option d'enchère dans laquelle le vendeur fixe un prix minimum pour le lot et le prix final doit le dépasser. A défaut, le lot reste invendu.

Dans cet exemple, nous travaillons avec un compte spécifiquement créé pour l'enchère. La durée des enchères est de 3000 0,001 blocs et le prix de départ du lot est de XNUMX WAVES. Un participant peut placer une enchère en envoyant une DataTransaction avec la clé « prix » et la valeur de son enchère.

Le prix de la nouvelle enchère doit être supérieur au prix actuel de cette clé, et le participant doit avoir au moins [new_bid + commission] tokens sur son compte. L'adresse de l'enchérisseur doit être enregistrée dans le champ « expéditeur » de DataTransaction, et la hauteur actuelle du bloc d'offre doit être comprise dans la période d'enchère.

Si à la fin de l'enchère le participant a fixé le prix le plus élevé, il peut envoyer une ExchangeTransaction pour payer le lot correspondant au prix et à la paire de devises spécifiés.

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. Enchères néerlandaises à prix dégressifs

Dans une vente aux enchères néerlandaise, un lot est initialement proposé à un prix supérieur à ce que l'acheteur est prêt à payer. Le prix est réduit progressivement jusqu'à ce qu'un des participants accepte d'acheter le lot au prix en vigueur.

Dans cet exemple, nous utilisons les mêmes constantes que dans le précédent, ainsi que le pas de prix lorsque le delta diminue. Le script du compte vérifie si le participant est bien le premier à placer un pari. Sinon, la DataTransaction n'est pas acceptée par la 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. Enchères « tout payant »

« All-pay » est une vente aux enchères dans laquelle tous les participants paient l'enchère, quel que soit celui qui remporte le lot. Chaque nouveau participant paie une enchère et celui qui fait l'enchère maximale remporte le lot.

Dans notre exemple, chaque participant aux enchères place une enchère via DataTransaction avec (clé, valeur)* = (« gagnant », adresse), (« prix », prix). Une telle DataTransaction n'est approuvée que si ce participant dispose déjà d'une TransferTransaction avec sa signature et que son enchère est supérieure à toutes les précédentes. L'enchère se poursuit jusqu'à ce que endHeight soit atteint.

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

Assurance / Financement participatif

Considérons une situation dans laquelle vous devez assurer les actifs des utilisateurs contre les pertes financières. Par exemple, un utilisateur souhaite avoir la garantie que si un jeton se déprécie, il pourra récupérer le montant total payé pour ces jetons et est prêt à payer un montant d'assurance raisonnable.

Pour mettre en œuvre cela, des « jetons d’assurance » doivent être émis. Ensuite, un script est installé sur le compte du preneur d’assurance, permettant d’exécuter uniquement les ExchangeTransactions qui remplissent certaines conditions.

Pour éviter les doubles dépenses, vous devez demander à l'utilisateur d'envoyer au préalable une DataTransaction sur le compte du preneur d'assurance avec (key, value) = (purchaseTransactionId, sellOrderId) et interdire l'envoi de DataTransactions avec une clé déjà utilisée.

Par conséquent, les preuves de l’utilisateur doivent contenir l’ID de transaction de l’achat du jeton d’assurance. La paire de devises doit être la même que celle utilisée lors de la transaction d'achat. Le coût doit également être égal à celui fixé au moment de l’achat, diminué du prix de l’assurance.

Il est entendu que par la suite le compte d'assurance achète des jetons d'assurance à l'utilisateur à un prix non inférieur à celui auquel il les a achetés : le compte d'assurance crée une ExchangeTransaction, l'utilisateur signe la commande (si la transaction est correctement réalisée), le le compte d'assurance signe la deuxième commande et l'intégralité de la transaction et l'envoie à la blockchain.

Si aucun achat n'a lieu, l'utilisateur peut créer une ExchangeTransaction selon les règles décrites dans le script et envoyer la transaction à la blockchain. De cette façon, l'utilisateur peut restituer l'argent dépensé pour l'achat de jetons assurés.

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 jeton d’assurance peut devenir un actif intelligent, par exemple, pour interdire son transfert à des tiers.

Ce système peut également être mis en œuvre pour les jetons de financement participatif, qui sont restitués aux propriétaires si le montant requis n'a pas été collecté.

Taxes sur les transactions

Les contrats intelligents sont également applicables dans les cas où il est nécessaire de percevoir une taxe sur chaque transaction avec plusieurs types d'actifs. Cela peut être fait via un nouvel actif avec installé parrainage pour les transactions avec des actifs intelligents :

1. Nous émettons des FeeCoin, qui seront envoyés aux utilisateurs à un prix fixe : 0,01 WAVES = 0,001 FeeCoin.

2. Définissez le parrainage pour FeeCoin et le taux de change : 0,001 WAVES = 0,001 FeeCoin.

3. Définissez le script suivant pour la ressource intelligente :

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
}

Désormais, chaque fois que quelqu'un transfère N actifs intelligents, il vous donnera FeeCoin d'un montant de N/taxDivisor (qui peut être acheté chez vous à 10 *N/taxDivisor WAVES), et vous donnerez au mineur N/taxDivisor WAVES. En conséquence, votre bénéfice (impôt) sera de 9*N / taxDivisor WAVES.

Vous pouvez également effectuer une taxation à l'aide d'un script d'actif intelligent et de 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
}

Programmes de cashback et de fidélité

Le cashback est un type de programme de fidélité dans lequel l'acheteur récupère une partie du montant dépensé pour un produit ou un service.

Lors de la mise en œuvre de ce cas à l’aide d’un compte intelligent, nous devons vérifier les preuves de la même manière que nous l’avons fait dans le cas de l’assurance. Pour éviter les doubles dépenses, l'utilisateur doit envoyer une DataTransaction avec (key, value) = (purchaseTransactionId, cashbackTransactionId) avant de recevoir du cashback.

Nous devons également interdire les clés existantes utilisant DataTransaction. cashbackDivisor - unité divisée par la part du cashback. Ceux. si la part du cashback est de 0.1, alors 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)
}

Échange atomique

L'échange atomique permet aux utilisateurs d'échanger des actifs sans l'aide d'un échange. Avec un swap atomique, les deux participants à la transaction sont tenus de la confirmer dans un certain délai.

Si au moins un des participants ne confirme pas correctement la transaction dans le délai imparti pour la transaction, la transaction est annulée et l'échange n'a pas lieu.

Dans notre exemple, nous utiliserons le script de compte intelligent suivant :

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
}

Dans le prochain article, nous examinerons l’utilisation des comptes intelligents dans les instruments financiers tels que les options, les contrats à terme et les bons.

Source: habr.com

Ajouter un commentaire