📜 ⬆️ ⬇️

The use of smart accounts Waves. Part 1: from auctions to bonus programs

image

Blockchain is often associated only with cryptocurrencies, but the scope of the DLT technology is much wider. One of the most promising areas for the use of the blockchain is a smart contract that runs automatically and does not require trust between the parties who have concluded it.

RIDE - language for smart contracts

Waves has developed a special language for smart contracts - RIDE. His complete documentation is here . And here - article on this subject on Habré.
')
The RIDE contract is a predicate and returns “true” or “false” at the output. Accordingly, the transaction is either recorded in the blockchain, or rejected. Smart contract fully guarantees the fulfillment of specified conditions. The generation of transactions from the contract in RIDE is currently not possible.

Today, there are two types of Waves smart contracts: smart accounts and smart assets. A smart account is a regular user account, but a script is set up for it that controls all transactions. A smart account script might look like this:

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

tx is a processed transaction that we allow using the pattern-matching mechanism only if it is not a transfer transaction. Pattern matching in RIDE is used to check the type of transaction. In a script of a smart account all existing types of transactions can be processed.

Also, variables can be declared in the script, if-then-else constructions and other methods of full-fledged condition checking are used. In order for contracts to have provable completability and complexity (cost), which is easy to predict before commencing contract execution, RIDE does not contain loops and jump type operators.

Other features of Waves accounts include the presence of a “state,” that is, account status. In the state account you can write an infinite number of pairs (key, value) using the date-transaction (DataTransaction). Further this information can be processed both through the REST API and directly in the smart contract.

Each transaction can contain an array of proofs in which you can enter the participant’s signature, the ID of the transaction, etc.

Working with RIDE through IDE allows you to see the compiled type of the contract (if it is compiled), create new accounts and set scripts for it, as well as send transactions via the command line.

For a full cycle, including creating an account, installing a smart contract on it, and sending transactions, you can also use the library to interact with the REST API (for example, C #, C, Java, JavaScript, Python, Rust, Elixir). To start working with IDE, just press the NEW button.

The possibilities of applying smart contracts are wide: from banning transactions to specific addresses (“blacklist”) to complex dApps.

Now let's look at specific examples of the application of smart contracts in business: during auctions, insurance and creating loyalty programs.

Auctions

One of the conditions for a successful auction is transparency: participants must be sure that bidding is not possible. This can be achieved thanks to the blockchain, where the constant data on all bets and the time when they were made will be available to all participants.

On the Waves blockchain, bids can be recorded in the state of an auction account via DataTransaction.

You can also set the start and end time of the auction using block numbers: the frequency of block generation in the Waves blockchain is approximately 60 seconds.

1. English auction of the rising price

Participants in the English auction make bids, competing with each other. Each new rate must exceed the previous one. The auction ends when there is no longer anyone willing to exceed the last bid. In this case, the participant who made the highest bid must provide the stated amount.

There is also an auction option in which the seller sets the minimum price for the lot, and the final price must exceed it. Otherwise, the lot remains unsold.

In this example, we are working with an account specially created for the auction. The duration of the auction is 3000 blocks, and the initial price of the lot is 0.001 WAVES. A participant can make a bid by sending DataTransaction with the key “price” and the value of its bid.

The price of a new rate must be higher than the current price of this key, and the participant must have at least [new_bid + commission] tokens on the account. The participant's address must be recorded in the “sender” field in DataTransaction, and the current height of the bid block must be within the bounds of the auction period.

If at the end of the auction the participant assigned the highest price, he can send ExchangeTransaction to pay for the corresponding lot at the specified price and currency pair.

 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. Dutch auction of the falling price

At the Dutch auction, the lot is initially offered at a price higher than the one that the buyer is willing to pay. The price decreases step by step until one of the participants agrees to buy a lot at the current price.

In this example, we use the same constants as in the previous one, as well as the price step while decreasing delta. The account script checks if the participant is the first to place a bet. Otherwise, DataTransaction is not accepted by the 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. Auction "all-pay"

“All-pay” - an auction, all participants of which pay the bid, pay, regardless of who wins the lot. Each new participant pays a bet, and the participant who makes the maximum bet wins the lot.

In our example, each auction participant makes a bid via DataTransaction with (key, value) * = ("winner", address), ("price", price). Such DataTransaction is approved only if for this participant there is already a TransferTransaction with its signature and its rate is higher than all previous ones. The auction continues until reaching the height endHeight.

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

Insurance / Crowdfunding

Consider the situation when you need to insure user assets from financial losses. For example, the user wants to get a guarantee that in the event of a token depreciation, he will be able to return the full amount paid for these tokens, and is willing to pay a reasonable amount of insurance.

To do this, you need to issue "insurance tokens." Then a script is installed on the policyholder’s account, which allows to execute only those ExchangeTransactions that meet certain conditions.

To prevent double waste, you need to request the user to send the DataTransaction in advance to the insurer's account with (key, value) = (purchaseTransactionId, sellOrderId) and prohibit sending the DataTransactions with the key already used.

Therefore, the user proofs must contain the transaction ID of the insurance token purchase. The currency pair must be the same as in the purchase transaction. The cost should also be equal to the one fixed at the time of purchase, minus the price of insurance.

It is implied that subsequently the insurance account buys insurance tokens from the user at a price not lower than the one for which he acquired them: the insurance account creates Exchange Transaction, the user signs the order (if the transaction is correct), the insurance account signs the second order and the entire transaction and sends it to the blockchain .

If the purchase does not occur, the user can create ExchangeTransaction in accordance with the rules described in the script, and send the transaction to the blockchain. So the user can return the money spent on the purchase of insured tokens.

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

An insurance token can be made a smart asset - for example, to prohibit its transfer to third parties.

This scheme can also be implemented for crowdfunding tokens, which are returned to the owners if the required amount has not been collected.

Transaction taxes

Smart contracts are also applicable in cases when it is necessary to collect tax from each transaction with several types of assets. This can be done through a new asset with established sponsorship for transactions with smart Assets:

1. We release FeeCoin, which will be sent to users at a fixed price: 0.01 WAVES = 0.001 FeeCoin.

2. Set the sponsorship for FeeCoin and the exchange rate: 0.001 WAVES = 0.001 FeeCoin.

3. Set the following script for smart asset:

 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 } 

Now every time someone transfers N smart assets, he will give you FeeCoin in the amount of N / taxDivisor (which can be bought from you at 10 * N / taxDivisor WAVES), and you will give to the Miner N / taxDivisor WAVES. As a result, your profit (tax) will be 9 * N / taxDivisor WAVES.

You can also tax using the smart asset script and 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 and loyalty programs

Cashback is a type of loyalty program in which a portion of the amount spent on a product or service is returned to the customer.

When implementing this case using a smart account, we must check the proofs just as we did in the insurance case. To prevent double waste, the user must send a DataTransaction with (key, value) = (purchaseTransactionId, cashbackTransactionId) before receiving a cashback.

We must also prohibit existing keys using DataTransaction. cashbackDivisor is a unit divided by a cashback share. Those. if the share of cashback is 0.1, then cashback Divisor 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) } 

Atomic swap

Atomic swap allows users to exchange assets without the help of the exchange. In an atomic swap, both parties to the transaction are required to confirm it within a certain period of time.

If at least one of the participants does not provide a correct confirmation of the transaction within the time allotted for the transaction, the transaction is canceled and no exchange takes place.

In our example, we will use the following smart account script:

 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 } 

In the next article, we will look at the use of smart accounts in financial instruments such as options, futures, and bills of exchange.

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


All Articles