Aplikace chytrých účtů Waves: od aukcí po bonusové programy

Aplikace chytrých účtů Waves: od aukcí po bonusové programy

Blockchain je často spojován pouze s kryptoměnami, ale oblasti uplatnění technologie DLT jsou mnohem širší. Jednou z nejslibnějších oblastí pro využití blockchainu je chytrá smlouva, která se provádí automaticky a nevyžaduje důvěru mezi stranami, které ji uzavřely.

RIDE – jazyk pro chytré smlouvy

Společnost Waves vyvinula speciální jazyk pro chytré smlouvy – RIDE. Jeho kompletní dokumentace je umístěna zde. A tady - článek na toto téma na Habr.

Kontrakt RIDE je predikát a jako výstup vrací „true“ nebo „false“. V souladu s tím je transakce buď zaznamenána v blockchainu, nebo odmítnuta. Smart kontrakt plně garantuje splnění stanovených podmínek. Generování transakcí ze smlouvy v RIDE aktuálně není možné.

Dnes existují dva typy chytrých smluv Waves: chytré účty a inteligentní aktiva. Chytrý účet je běžný uživatelský účet, ale je pro něj nastaven skript, který řídí všechny transakce. Skript chytrého účtu může vypadat například takto:

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

tx je zpracovávaná transakce, kterou povolujeme pomocí mechanismu porovnávání vzorů pouze v případě, že se nejedná o převodní transakci. Párování vzorů v RIDE se používá ke kontrole typu transakce. Všechny existující účty lze zpracovat ve skriptu chytrého účtu typy transakcí.

Skript může také deklarovat proměnné, používat konstrukce „if-then-else“ a další metody pro plnou kontrolu podmínek. Aby bylo zajištěno, že smlouvy mají prokazatelnou úplnost a složitost (náklady), které lze snadno předvídat před zahájením realizace smlouvy, RIDE neobsahuje smyčky nebo skokové příkazy.

Mezi další funkce účtů Waves patří přítomnost „stavu“, tedy stavu účtu. Pomocí datových transakcí (DataTransaction) můžete do stavu účtu zapisovat nekonečné množství párů (klíč, hodnota). Tyto informace je pak možné zpracovat jak přes REST API, tak přímo ve smart kontraktu.

Každá transakce může obsahovat řadu dokladů, do kterých lze zadat podpis účastníka, ID požadované transakce atd.

Práce s RIDE via IDE umožňuje vidět zkompilovaný pohled na smlouvu (pokud je zkompilovaná), vytvářet nové účty a nastavovat pro ni skripty a také odesílat transakce přes příkazový řádek.

Pro celý cyklus, včetně vytvoření účtu, instalace chytré smlouvy na něj a odesílání transakcí, můžete také použít knihovnu pro interakci s REST API (například C#, C, Java, JavaScript, Python, Rust, Elixir) . Chcete-li začít pracovat s IDE, stačí kliknout na tlačítko NOVÝ.

Možnosti využití smart kontraktů jsou široké: od zákazu transakcí na určité adresy („černá listina“) až po komplexní dApps.

Nyní se podívejme na konkrétní příklady využití chytrých kontraktů v podnikání: při provádění aukcí, pojištění a vytváření věrnostních programů.

Aukce

Jednou z podmínek úspěšné aukce je transparentnost: účastníci musí mít jistotu, že není možné manipulovat s nabídkami. Toho lze dosáhnout díky blockchainu, kde budou všem účastníkům k dispozici neměnná data o všech sázkách a čase, kdy byly uzavřeny.

Na blockchainu Waves lze nabídky zaznamenávat ve stavu aukčního účtu prostřednictvím DataTransaction.

Můžete také nastavit čas začátku a konce aukce pomocí čísel bloků: frekvence generování bloků v blockchainu Waves je přibližně rovna 60 sekundy.

1. Anglická aukce se vzestupnou cenou

Účastníci anglické aukce si navzájem konkurují. Každá nová sázka musí převýšit tu předchozí. Aukce končí, když už nejsou žádní další dražitelé, kteří by překročili poslední příhoz. V tomto případě musí uvedenou částku poskytnout uchazeč s nejvyšší nabídkou.

Existuje také aukce, ve které prodávající stanoví minimální cenu za los a konečná cena ji musí překročit. V opačném případě zůstává pozemek neprodán.

V tomto příkladu pracujeme s účtem speciálně vytvořeným pro aukci. Délka aukce je 3000 bloků a vyvolávací cena lotu je 0,001 VLNY. Účastník může podat nabídku zasláním DataTransaction s klíčem „cena“ a hodnotou své nabídky.

Cena nového příhozu musí být vyšší než aktuální cena za tento klíč a účastník musí mít na účtu alespoň [nová_nabídka + provize] tokenů. Adresa nabízejícího musí být zaznamenána v poli "odesílatel" v DataTransaction a aktuální výška bloku nabídky musí být v rámci aukčního období.

Pokud na konci aukce účastník nastavil nejvyšší cenu, může poslat ExchangeTransaction, aby zaplatil odpovídající lot za zadanou cenu a měnový pár.

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. Nizozemská aukce klesajících cen

V nizozemské aukci se zpočátku nabízí hodně za cenu vyšší, než jakou je kupující ochoten zaplatit. Cena se postupně snižuje, dokud jeden z účastníků nesouhlasí s nákupem pozemku za aktuální cenu.

V tomto příkladu používáme stejné konstanty jako v předchozím a také cenový krok, když se delta snižuje. Skript účtu kontroluje, zda je účastník skutečně prvním, kdo vsadil. V opačném případě nebude DataTransaction blockchainem přijat.

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. Aukce „vše platí“

„All-pay“ je aukce, ve které platí nabídku všichni účastníci bez ohledu na to, kdo vyhraje los. Každý nový účastník zaplatí nabídku a účastník, který učiní maximální nabídku, vyhrává los.

V našem příkladu každý účastník aukce zadá nabídku prostřednictvím DataTransaction s (klíč, hodnota)* = („vítěz“, adresa), („cena“, cena). Taková DataTransakce je schválena pouze v případě, že tento účastník již má Převodovou transakci se svým podpisem a jeho nabídka je vyšší než všechny předchozí. Aukce pokračuje, dokud není dosaženo 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)
}

Pojištění / Crowdfunding

Uvažujme situaci, kdy potřebujete pojistit majetek uživatelů proti finančním ztrátám. Uživatel chce například záruku, že pokud se token znehodnotí, bude moci získat zpět celou částku zaplacenou za tyto tokeny a je ochoten zaplatit přiměřenou částku pojištění.

Aby to bylo možné provést, je třeba vydat „pojistné tokeny“. Poté se na účet pojistníka nainstaluje skript, který umožní provést pouze ty ExchangeTransactions, které splňují určité podmínky.

Abyste zabránili dvojímu utrácení, musíte předem požádat uživatele o zaslání DataTransaction na účet pojistníka s (klíč, hodnota) = (purchaseTransactionId, sellOrderId) a zakázat odesílání DataTransactions s klíčem, který již byl použit.

Proto musí uživatelské doklady obsahovat ID transakce nákupu pojistného tokenu. Měnový pár musí být stejný jako v nákupní transakci. Cena musí být rovněž stejná jako cena stanovená v době nákupu, snížená o cenu pojištění.

Rozumí se, že následně pojistný účet zakoupí od uživatele pojistné tokeny za cenu, která není nižší, než za kterou je zakoupil: pojistný účet vytvoří ExchangeTransaction, uživatel podepíše objednávku (pokud je transakce dokončena správně), pojišťovací účet podepíše druhou objednávku a celou transakci a odešle ji na blockchain.

Pokud nedojde k nákupu, může uživatel vytvořit ExchangeTransaction podle pravidel popsaných ve skriptu a odeslat transakci do blockchainu. Uživatel tak může vrátit peníze vynaložené na nákup pojištěných tokenů.

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

Pojistný token může být chytrým aktivem, například za účelem zákazu jeho převodu na třetí strany.

Toto schéma lze zavést i pro crowdfundingové tokeny, které se v případě nevysbírání požadované částky vrátí majitelům.

Daně z transakcí

Smart kontrakty jsou použitelné i v případech, kdy je nutné vybrat daň z každé transakce s více druhy aktiv. To lze provést prostřednictvím nového aktiva s nainstalovaným sponzorství pro transakce s chytrými aktivy:

1. Vydáváme FeeCoin, který bude uživatelům zasílán za pevnou cenu: 0,01 VLNY = 0,001 FeeCoin.

2. Nastavte sponzorství pro FeeCoin a směnný kurz: 0,001 VLNY = 0,001 FeeCoin.

3. Nastavte následující skript pro inteligentní aktivum:

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
}

Nyní pokaždé, když někdo převede N chytrých aktiv, dá vám FeeCoin ve výši N/taxDivisor (který lze u vás zakoupit za 10 *N/taxDivisor VLNY) a vy dáte těžaři N/taxDivisor VLNY. V důsledku toho bude váš zisk (daň) 9*N / daňDivisor WAVES.

Zdanění můžete provést také pomocí inteligentního skriptu aktiv a 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 a věrnostní programy

Cashback je druh věrnostního programu, ve kterém kupující dostane zpět část částky utracené za produkt nebo službu.

Při realizaci tohoto případu pomocí chytrého účtu musíme důkazy zkontrolovat stejným způsobem jako v případě pojištění. Aby se zabránilo dvojímu utrácení, musí uživatel před obdržením cashback odeslat DataTransaction s (klíč, hodnota) = (purchaseTransactionId, cashbackTransactionId).

Musíme také nastavit zákaz existujících klíčů pomocí DataTransaction. cashbackDivisor - jednotka dělená podílem cashback. Tito. pokud je podíl cashbacku 0.1, pak 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)
}

Výměna atomů

Atomic swap umožňuje uživatelům vyměňovat aktiva bez pomoci burzy. U atomového swapu jsou oba účastníci transakce povinni jej potvrdit do určité doby.

Pokud alespoň jeden z účastníků neposkytne správné potvrzení transakce ve lhůtě určené pro transakci, transakce je zrušena a směna neproběhne.

V našem příkladu použijeme následující skript chytrého účtu:

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
}

V příštím článku se podíváme na využití chytrých účtů ve finančních nástrojích, jako jsou opce, futures a směnky.

Zdroj: www.habr.com

Přidat komentář