La blockchain est souvent associée uniquement aux crypto-monnaies, mais le champ d'application de la technologie DLT est beaucoup plus large. L'un des domaines les plus prometteurs pour l'utilisation de la blockchain est un contrat intelligent qui s'exécute automatiquement et ne nécessite pas de confiance entre les parties qui l'ont conclue.
RIDE - langage pour les contrats intelligentsWaves a développé un langage spécial pour les contrats intelligents - RIDE. Sa documentation complète est
ici . Et ici -
un article sur ce sujet sur Habré.
Le contrat sur RIDE est un prédicat et renvoie «vrai» ou «faux» sur la sortie. En conséquence, une transaction est soit écrite dans la blockchain, soit rejetée. Un contrat intelligent garantit pleinement le respect des conditions spécifiées. La génération de transactions à partir d'un contrat dans RIDE n'est actuellement pas possible.
Aujourd'hui, il existe deux types de contrats intelligents Waves: les comptes intelligents et les actifs intelligents. Un compte intelligent est un compte d'utilisateur normal, mais un script est défini pour celui-ci qui contrôle toutes les transactions. Un script de compte intelligent pourrait ressembler à ceci:
match tx { case t: TransferTransaction | MassTransferTransaction => false case _ => true }
tx est une transaction traitée que nous autorisons à utiliser le mécanisme de correspondance de modèle uniquement s'il ne s'agit pas d'une transaction de transfert. La correspondance de motifs RIDE est utilisée pour vérifier le type de transaction. Dans le script de compte intelligent, tous les
types de transactions existants peuvent être traités.
De plus, des variables peuvent être déclarées dans le script, des constructions «if-then-else» et d'autres méthodes pour la vérification complète des conditions peuvent être utilisées. Pour que les contrats aient une fin et une complexité (coût) prouvables, ce qui est facile à prévoir avant le début du contrat, RIDE ne contient pas de boucles et d'opérateurs comme jump.
Parmi les autres caractéristiques des comptes Waves, il y a la présence d'un «état», c'est-à-dire l'état du compte. Un nombre infini de paires (clé, valeur) peut être écrit dans l'état du compte à l'aide de transactions de données (DataTransaction). De plus, ces informations peuvent être traitées à la fois via l'API REST et directement dans le contrat intelligent.
Chaque transaction peut contenir un tableau d’épreuves, dans lequel vous pouvez saisir la signature du participant, l’identifiant de la transaction nécessaire, etc.
Travailler avec RIDE via l'
IDE vous permet de voir la forme 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, y compris la création d'un compte, l'installation d'un contrat intelligent et l'envoi de transactions, vous pouvez également utiliser la bibliothèque pour interagir avec l'API REST (par exemple, C #, C, Java, JavaScript, Python, Rust, Elixir). Pour commencer à travailler avec l'IDE, cliquez simplement sur le bouton NOUVEAU.
Les possibilités d'utilisation des contrats intelligents sont larges: de l'interdiction des transactions à certaines adresses (la "liste noire") aux dApps complexes.
Examinons maintenant des exemples spécifiques de l'utilisation de contrats intelligents dans les entreprises: lors des enchères, de l'assurance et de la création de programmes de fidélité.
EnchèresUne des conditions de réussite des enchères est la transparence: les soumissionnaires doivent être sûrs qu'il est impossible de manipuler les enchères. Cela peut être réalisé grâce à la blockchain, où des données inchangées 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 bloc dans la chaîne de blocs Waves est d'environ
60 secondes.
1. Vente aux enchères en hausse en anglaisLes participants à l'enchère anglaise, en concurrence les uns avec les autres. Chaque nouveau pari doit dépasser le précédent. L'enchère se termine lorsqu'il n'y a plus de volonté de dépasser la dernière enchère. Dans ce cas, le plus offrant doit fournir le montant déclaré.
Il existe également une option d'enchère dans laquelle le vendeur fixe le prix minimum du lot et le prix final doit le dépasser. Sinon, le lot reste invendu.
Dans cet exemple, nous travaillons avec un compte spécialement créé pour l'enchère. La durée de l'enchère est de 3000 blocs et le prix initial du lot est de 0,001 ONDES. Un participant peut faire un pari en envoyant une DataTransaction avec la clé «prix» et la valeur de son offre, dans les preuves de transaction, vous devez ajouter la clé publique et la signature de l'expéditeur.
Le prix du nouveau pari doit être supérieur au prix actuel de cette clé, et le participant doit avoir au moins [new_state + commission] jetons sur le compte. L'adresse du soumissionnaire doit être entrée dans le champ "expéditeur" dans DataTransaction, et la hauteur actuelle du bloc d'enchères doit être comprise dans la période de l'enchère.
Si à la fin de l'enchère, l'enchérisseur a fixé le prix le plus élevé, il peut envoyer ExchangeTransaction pour payer le lot correspondant au prix et à la paire de devises indiqué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 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. Vente aux enchères néerlandaise de prix en baisseLors d'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 que l'un des participants accepte d'acheter le lot au prix actuel.
Dans cet exemple, nous utilisons les mêmes constantes que dans la précédente, ainsi que le pas de prix lors de l'abaissement du delta. Le script de compte vérifie si le participant est vraiment le premier à parier. Dans les preuves de transaction, vous devez ajouter la clé publique et la signature de l'expéditeur, sinon DataTransaction n'est pas accepté 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")) # 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. Enchères entièrement payantes«Tout payer» - une enchère, dont tous les participants paient l'enchère, quelle que soit la personne qui remporte le lot. Chaque nouveau participant paie la mise, et le participant qui a fait la mise maximale remporte le lot.
Dans notre exemple, chaque participant à une enchère fait une offre via une DataTransaction avec (clé, valeur) * = ("gagnant", adresse), ("prix", prix). Une telle DataTransaction n'est approuvée que si pour ce participant il existe déjà une TransferTransaction avec sa signature et son tarif est supérieur à tous les précédents. L'enchère se poursuit jusqu'à la fin de la hauteur.
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) }
Assurance / CrowdfundingConsidérez une situation où vous devez assurer les actifs des utilisateurs contre les pertes financières. Par exemple, l'utilisateur souhaite obtenir la garantie que si le jeton se déprécie, il sera en mesure de rembourser le montant total payé pour ces jetons et est prêt à payer un montant d'assurance raisonnable.
Pour mettre en œuvre cela, vous devez émettre des "jetons d'assurance". Ensuite, un script est installé sur le compte du titulaire de police qui vous permet d'exécuter uniquement les ExchangeTransactions qui remplissent certaines conditions.
Pour éviter les doubles dépenses, vous devez demander à l'utilisateur d'envoyer DataTransaction à l'avance au compte de l'assuré avec (clé, valeur) = (PurchaseTransactionId, sellOrderId) et interdire l'envoi de DataTransactions avec la clé déjà utilisée.
Par conséquent, les preuves utilisateur doivent contenir l'ID de transaction de l'achat du jeton d'assurance. La paire de devises doit être la même que lors de la transaction d'achat. Le coût doit également être égal à celui enregistré au moment de l'achat, moins le prix de l'assurance.
Il est entendu que par la suite le compte d'assurance rachète les jetons d'assurance à l'utilisateur à un prix non inférieur au prix auquel il les a achetés: le compte d'assurance crée ExchangeTransaction, l'utilisateur signe la commande (si la transaction est correctement effectuée), le compte d'assurance signe la deuxième commande et la transaction entière et l'envoie à la blockchain .
Si l'achat ne se produit pas, l'utilisateur peut créer une commande conformément aux règles décrites dans le script et envoyer la transaction à la blockchain. Ainsi, 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 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 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 retournés aux propriétaires si le montant nécessaire n'a pas été collecté.
Taxes sur les transactionsLes contrats intelligents sont également applicables dans les cas où il est nécessaire de collecter la taxe sur chaque transaction avec plusieurs types d'actifs. Cela peut être fait via un nouvel actif
sponsorisé pour les transactions avec des actifs intelligents:
1. Nous publions FeeCoin, qui sera envoyé aux utilisateurs à un prix fixe: 0,01 WAVES = 0,001 FeeCoin.
2. Nous avons mis en place un parrainage pour FeeCoin et un taux de change: 0,001 WAVES = 0,001 FeeCoin.
3. Nous définissons le script suivant pour l'actif intelligent:
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 }
Maintenant, chaque fois que quelqu'un transfère N actifs intelligents, il vous donnera des FeeCoins d'un montant de N / taxDivisor (qui peut être acheté auprès de vous pour 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 / impôt du diviseur d'ondes.
Vous pouvez également taxer à 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éLa remise en argent est un type de programme de fidélisation dans lequel une partie du montant dépensé pour un produit ou un service est retournée à l'acheteur.
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 de doubler les dépenses, avant de recevoir un cashback, l'utilisateur doit envoyer un DataTransaction avec (clé, valeur) = (PurchaseTransactionId, cashbackTransactionId).
Nous devons également interdire les clés existantes à l'aide d'une DataTransaction. cashbackDivisor - une unité divisée par la part de cashback. C'est-à-dire si la part de 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 atomiqueLe swap atomique permet aux utilisateurs d'échanger des actifs sans l'aide d'un échange. Dans un échange atomique, les deux participants à la transaction doivent la confirmer dans un certain délai.
Si au moins l'un des participants ne fournit pas la confirmation correcte de la transaction dans le délai imparti pour la transaction, la transaction est annulée et aucun échange n'a 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 considérerons l'utilisation de comptes intelligents dans les instruments financiers - tels que les options, les futures et les factures.