Приложение на смарт акаунти Waves: от търгове до бонус програми

Приложение на смарт акаунти Waves: от търгове до бонус програми

Блокчейн често се свързва само с криптовалути, но областите на приложение на DLT технологията са много по-широки. Една от най-обещаващите области за използване на блокчейн е интелигентен договор, който се изпълнява автоматично и не изисква доверие между страните, които са го сключили.

RIDE – език за интелигентни договори

Waves разработи специален език за интелигентни договори – RIDE. Намира се пълната му документация тук. И тук - статия по тази тема на Хабр.

Договорът RIDE е предикат и връща „true“ или „false“ като изход. Съответно транзакцията или се записва в блокчейна, или се отхвърля. Умният договор гарантира напълно изпълнението на посочените условия. Генерирането на транзакции от договор в RIDE в момента не е възможно.

Днес има два вида интелигентни договори на Waves: интелигентни акаунти и интелигентни активи. Интелигентният акаунт е обикновен потребителски акаунт, но за него е зададен скрипт, който контролира всички транзакции. Скрипт за интелигентен акаунт може да изглежда така, например:

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

tx е транзакция, която се обработва, и позволяваме използването на механизма за съпоставяне на образец само ако не е транзакция за прехвърляне. Съпоставянето на шаблони в RIDE се използва за проверка на типа транзакция. Всички съществуващи акаунти могат да бъдат обработени в скрипта за интелигентен акаунт видове транзакции.

Скриптът може също така да декларира променливи, да използва конструкции „if-then-else“ и други методи за пълна проверка на условията. За да се гарантира, че договорите имат доказуема пълнота и сложност (цена), която е лесна за прогнозиране, преди да започне изпълнението на договора, RIDE не съдържа цикли или оператори за прескачане.

Други характеристики на акаунтите в Waves включват наличието на „състояние“, т.е. състоянието на акаунта. Можете да запишете безкраен брой двойки (ключ, стойност) в състоянието на акаунта, като използвате транзакции с данни (DataTransaction). След това тази информация може да се обработва както чрез REST API, така и директно в интелигентния договор.

Всяка транзакция може да съдържа масив от доказателства, в които да се въведе подпис на участника, ID на необходимата транзакция и др.

Работа с RIDE чрез IDE ви позволява да видите компилирания изглед на договора (ако е компилиран), да създавате нови акаунти и да задавате скриптове за него, както и да изпращате транзакции чрез командния ред.

За пълен цикъл, включително създаване на акаунт, инсталиране на интелигентен договор върху него и изпращане на транзакции, можете също да използвате библиотека за взаимодействие с REST API (например C#, C, Java, JavaScript, Python, Rust, Elixir) . За да започнете работа с IDE, просто щракнете върху бутона НОВ.

Възможностите за използване на интелигентни договори са широки: от забрана на транзакции към определени адреси („черен списък“) до сложни dApps.

Сега нека да разгледаме конкретни примери за използването на интелигентни договори в бизнеса: при провеждане на търгове, застраховки и създаване на програми за лоялност.

Аукциони

Едно от условията за успешен търг е прозрачността: участниците трябва да са уверени, че е невъзможно да се манипулират офертите. Това може да се постигне благодарение на блокчейна, където неизменни данни за всички залози и времето, когато са направени, ще бъдат достъпни за всички участници.

В блокчейна на Waves офертите могат да се записват в състоянието на аукционния акаунт чрез DataTransaction.

Можете също да зададете началния и крайния час на търга, като използвате номера на блокове: честотата на генериране на блокове в блокчейна на Waves е приблизително равна на 60 секунди.

1. Английски търг с възходяща цена

Участниците в английския търг наддават в конкуренция помежду си. Всеки нов залог трябва да надвишава предишния. Търгът приключва, когато няма наддаващи, които да надхвърлят последната оферта. В този случай предложилият най-висока цена трябва да предостави посочената сума.

Има и опция за търг, при която продавачът определя минимална цена за лота, като крайната цена трябва да я надвишава. В противен случай партидата остава непродадена.

В този пример работим с акаунт, специално създаден за търга. Продължителността на аукциона е 3000 блока, а началната цена на лота е 0,001 WAVES. Участник може да направи оферта, като изпрати DataTransaction с ключа „цена“ и стойността на своята оферта.

Цената на новата оферта трябва да е по-висока от текущата цена за този ключ и участникът трябва да има поне [new_bid + комисионна] токени в акаунта си. Адресът на оферента трябва да бъде записан в полето „подател“ в DataTransaction, а текущата височина на блока за оферти трябва да е в рамките на периода на търга.

Ако в края на аукциона участникът е поставил най-високата цена, той може да изпрати ExchangeTransaction, за да заплати съответния лот на определената цена и валутна двойка.

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. Холандски търг на намаляващи цени

В холандски аукцион партидата първоначално се предлага на цена, по-висока от тази, която купувачът е готов да плати. Цената намалява стъпка по стъпка, докато един от участниците се съгласи да закупи лота на текущата цена.

В този пример използваме същите константи като в предишния, както и ценовата стъпка, когато делтата намалява. Скриптът на акаунта проверява дали участникът наистина е първият, който е направил залог. В противен случай DataTransaction не се приема от блокчейна.

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. Търг „всичко заплащане“

“All-pay” е търг, в който всички участници плащат наддаването, независимо кой печели партидата. Всеки нов участник плаща оферта, а участникът, който направи максималната оферта, печели лота.

В нашия пример всеки участник в търга поставя оферта чрез DataTransaction с (ключ, стойност)* = (“победител”, адрес),(“цена”, цена). Такава DataTransaction се одобрява само ако този участник вече има TransferTransaction с неговия подпис и офертата му е по-висока от всички предишни. Търгът продължава до достигане на 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)
}

Застраховка / Групово финансиране

Нека разгледаме ситуация, в която трябва да застраховате активите на потребителите срещу финансови загуби. Например, потребител иска гаранция, че ако даден токен се обезцени, той ще може да си върне пълната сума, платена за тези токени, и е готов да плати разумна сума застраховка.

За да се приложи това, трябва да бъдат издадени „застрахователни токени“. След това в акаунта на притежателя на полицата се инсталира скрипт, който позволява да бъдат изпълнени само тези ExchangeTransactions, които отговарят на определени условия.

За да предотвратите двойно харчене, трябва предварително да поискате от потребителя да изпрати DataTransaction до акаунта на притежателя на полицата с (ключ, стойност) = (purchaseTransactionId, sellOrderId) и да забраните изпращането на DataTransaction с ключ, който вече е бил използван.

Следователно доказателствата на потребителя трябва да съдържат идентификационния номер на транзакцията на покупката на застрахователния токен. Валутната двойка трябва да е същата като при транзакцията за покупка. Цената също трябва да бъде равна на тази, фиксирана в момента на покупката, минус цената на застраховката.

Разбираемо е, че впоследствие застрахователният акаунт купува застрахователни токени от потребителя на цена, не по-ниска от тази, на която ги е закупил: застрахователният акаунт създава ExchangeTransaction, потребителят подписва поръчката (ако транзакцията е завършена правилно), застрахователна сметка подписва втората поръчка и цялата транзакция и я изпраща към блокчейна.

Ако не се извърши покупка, потребителят може да създаде ExchangeTransaction съгласно правилата, описани в скрипта, и да изпрати транзакцията към блокчейна. По този начин потребителят може да върне парите, изразходвани за закупуване на застраховани токени.

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

Застрахователен токен може да бъде направен интелигентен актив, например, за да се забрани прехвърлянето му на трети страни.

Тази схема може да се приложи и за токени за групово финансиране, които се връщат на собствениците, ако не е събрана необходимата сума.

Данъци върху транзакциите

Интелигентните договори са приложими и в случаите, когато е необходимо да се събира данък върху всяка сделка с няколко вида активи. Това може да стане чрез нов актив с инсталиран спонсорство за транзакции с интелигентни активи:

1. Издаваме FeeCoin, който ще бъде изпратен на потребителите на фиксирана цена: 0,01 WAVES = 0,001 FeeCoin.

2. Задайте спонсорство за FeeCoin и обменен курс: 0,001 WAVES = 0,001 FeeCoin.

3. Задайте следния скрипт за интелигентния актив:

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
}

Сега всеки път, когато някой прехвърли N интелигентни активи, той ще ви даде FeeCoin в размер на N/taxDivisor (които могат да бъдат закупени от вас на 10 *N/taxDivisor WAVES), а вие ще дадете на копача N/taxDivisor WAVES. В резултат на това вашата печалба (данък) ще бъде 9*N / taxDivisor WAVES.

Можете също така да извършвате данъчно облагане, като използвате скрипт за интелигентни активи и 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 е вид програма за лоялност, при която купувачът получава обратно част от сумата, изразходвана за продукт или услуга.

Когато изпълняваме този случай с помощта на интелигентен акаунт, трябва да проверим доказателствата по същия начин, както направихме в застрахователния случай. За да предотврати двойно харчене, потребителят трябва да изпрати DataTransaction с (ключ, стойност) = (purchaseTransactionId, cashbackTransactionId) преди да получи връщане на пари.

Трябва също така да забраним съществуващи ключове, използващи DataTransaction. cashbackDivisor - единица, разделена на дяла на cashback. Тези. ако кешбек дялът е 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)
}

Атомен суап

Atomic суап позволява на потребителите да обменят активи без помощта на обмен. При атомен суап и двамата участници в транзакцията са длъжни да я потвърдят в рамките на определен период от време.

Ако поне един от участниците не предостави правилно потвърждение на транзакцията в рамките на времето, определено за транзакцията, транзакцията се анулира и обменът не се осъществява.

В нашия пример ще използваме следния скрипт за интелигентен акаунт:

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
}

В следващата статия ще разгледаме използването на интелигентни акаунти във финансови инструменти като опции, фючърси и сметки.

Източник: www.habr.com

Добавяне на нов коментар