Aplikácia inteligentných účtov Waves a inteligentných aktív vo finančných nástrojoch

Aplikácia inteligentných účtov Waves a inteligentných aktív vo finančných nástrojoch

V predošlom článok Pozreli sme sa na niekoľko prípadov použitia inteligentných účtov v podnikaní, vrátane aukcií a vernostných programov.
Dnes si povieme, ako môžu inteligentné účty a inteligentné aktíva zlepšiť transparentnosť a spoľahlivosť finančných nástrojov, ako sú opcie, futures a zmenky.

Možnosť

Opcia je burzovo obchodovaný kontrakt, ktorý dáva kupujúcemu právo kúpiť aktívum za určitú cenu alebo pred určitým dátumom, ale nezaväzuje ho tak urobiť.

Túto možnosť je možné implementovať nasledovne:

Pre samotné opcie používame inteligentné aktívum ako nástroj a inteligentný účet pre účastníka, ktorý koná ako burza a vydáva opcie. Účastník burzy sa zaväzuje predať určité množstvo konkrétneho aktíva za cenu (sellPrice) medzi výškami blokov expirationStart a expirationEnd.

V kóde inteligentného aktíva jednoducho skontrolujeme, či sa obchoduje iba medzi zadanými výškami a nebudeme kontrolovať nič iné, pričom všetku zodpovednosť za dodržiavanie pravidiel ponecháme na kóde účastníka burzy.

Kód inteligentného majetku:

let expirationStart = 100000
let expirationEnd = 101440

match tx {
   case some : ExchangeTransaction | TransferTransaction =>
       height > expirationStart && height <= expirationEnd
   case _ => false
}

Predpokladajme nasledovné: účastník burzy predáva opcie na kúpu aktíva a ostatní účastníci môžu tieto opcie prevádzať alebo obchodovať. Aby potenciálny kupujúci uplatnil svoje právo na kúpu, musí previesť požadovaný počet opcií na účet predávajúceho, teda účastníka burzy. Následne zaznamená dokončený prevod do stavu účtu účastníka burzy a až potom môže burzová transakcia pokračovať podľa stanovených podmienok nákupu/predaja.

V kóde inteligentného účtu musíme zabezpečiť, aby každá transakcia ExchangeTransaction, ktorá ním prechádza pre finálnu transakciu nákupu/predaja, spĺňala stanovené podmienky a aby účastník nakúpil presne toľko jednotiek, koľko poslal na účet účastníka burzy. Potenciálny kupujúci musí odoslať platnú dátovú transakciu DataTransaction o dokončenom prevode, aby sa zabránilo dvojitému utrácaniu zo strany účastníka burzy. V tejto dátovej transakcii kupujúci vloží hodnotu rovnajúcu sa počtu opcií prevedených na účet účastníka burzy, t. j. počtu jednotiek aktíva, ktoré si môže kúpiť, pomocou kľúča zhodného s jeho adresou.

Kód inteligentného účtu:

#владелец аккаунта дает обязательство продать определенное количество юнитов ассета
#по цене sellPrice между высотами блоков expirationStart и expirationEnd

let expirationStart = 100000
let expirationEnd = 101440
let sellPrice = 10000
let amountAsset = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
let priceAsset = base58'9jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
#ID ассета-опциона
let optionsAsset = base58'7jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
#извлекаем из транзакции адрес отправителя
let this = tx.sender
match tx {
   case dataTx : DataTransaction =>
       #извлекаем количество юнитов из дата-транзакции по ключу (ID пользователя)
       let units = extract(getInteger(dataTx.data, dataTx.data[0].key))

       #извлекаем трансфер-транзакцию опционов из пруфа
       let e = transactionById(dataTx.proofs[2]) #
       match e {
           case transferTx : TransferTransaction =>
               #убеждаемся, что трансфер был на текущий адрес
               (transferTx.recipient == this) &&
               #убеждаемся, что отправитель транзакции написал в качестве ключа свой ID
               dataTx.data[0].key == toBase58String(transferTx.sender.bytes) &&
               sigVerify(dataTx.bodyBytes, dataTx.proofs[0], transferTx.senderPublicKey) &&
               #убеждаемся, что указанное количество юнитов соответствует посланному количеству опционов
               (units == transferTx.amount) &&
               #убеждаемся, что был переведен именно ассет-опцион
               (transferTx.assetId == optionsAsset)              
           case _ => false
       } &&
       size(dataTx.data) == 1 && !isDefined(getInteger(this, dataTx.data[0].key))
       && height > expirationStart && height <= expirationEnd
   case exchangeTx : ExchangeTransaction =>
       #убеждаемся, что итоговый обмен происходит по указанным заранее правилам
       let correctAssetPair = exchangeTx.sellOrder.assetPair.amountAsset == amountAsset &&
                                   exchangeTx.sellOrder.assetPair.priceAsset == priceAsset
       let correctPrice = exchangeTx.sellOrder.price == sellPrice
       #извлекаем дата-транзакцию из пруфа      
       let d = transactionById(exchangeTx.proofs[2])
       match d{
           case dataTx : DataTransaction =>
               let buyOrderSender = dataTx.data[0].key
               toBase58String(exchangeTx.buyOrder.sender.bytes) == buyOrderSender &&
               exchangeTx.amount == extract(getInteger(dataTx.data, buyOrderSender))
           case _ => false
       } &&
       exchangeTx.sellOrder.sender == this &&
       correctAssetPair && correctPrice &&
       height > expirationStart && height <= expirationEnd
   case _ => false
}

Futures na inteligentných účtoch

Na rozdiel od opcie, futures kontrakt nie je právom, ale povinnosťou kupujúceho kúpiť aktívum za cenu stanovenú v zmluve v určitom bode v budúcnosti.

Vo všeobecnosti je realizácia futures kontraktu podobná realizácii opcie. V tomto prípade inteligentné aktívum funguje ako futures kontrakt.

Je tiež potrebné zabezpečiť, aby objednávku na kúpu podpísali kupujúci aj predávajúci. Futures kontrakt je záväzok, ktorý musí byť splnený v každom prípade. Preto, ak predávajúci alebo účastník nesplní svoje povinnosti, ktorýkoľvek účastník siete môže odoslať transakciu, čím splní futures kontrakt.

Skript inteligentného aktíva riadi všetky transakcie TransferTransactions a ExchangeTransactions futures aktíva a schvaľuje ich iba v prípade, že účastník kupujúci vytvoril objednávku na nákup futures aktíva od účastníka burzy.

Táto objednávka musí byť platná a musí spĺňať podmienky futures kontraktu. Na overenie objednávky môžete zadať všetky jej polia do stavu účtu kupujúceho spolu s bajtovou reprezentáciou podpísanej objednávky a potom ju externe overiť.

RIDE momentálne neobsahuje natívnu funkciu parsovania bajtov transakcií, ale obsahuje všetky potrebné nástroje. Vývojári si preto môžu túto funkciu vyskúšať implementovať sami.

Účet s viacerými podpismi / úschova

Účet s viacerými podpismi umožňuje viacerým používateľom spoločne spravovať aktíva (napríklad transakcie s aktívami môžu byť možné iba s podpismi troch zo štyroch používateľov). Na vytvorenie účtov s viacerými podpismi v jazyku RIDE môžeme použiť dôkazy o transakciách.

Účet s viacerými podpismi sa môže použiť aj ako úschovný účet, kde sú finančné prostriedky držané, kým si strany zmluvy nesplnia svoje záväzky.

let alicePubKey  = base58'5AzfA9UfpWVYiwFwvdr77k6LWupSTGLb14b24oVdEpMM'
let bobPubKey    = base58'2KwU4vzdgPmKyf7q354H9kSyX9NZjNiq4qbnH2wi2VDF'
let cooperPubKey = base58'GbrUeGaBfmyFJjSQb9Z8uTCej5GzjXfRDVGJGrmgt5cD'
#выясняем, кто предоставил корректные подписи
let aliceSigned  = if(sigVerify(tx.bodyBytes, tx.proofs[0], alicePubKey)) then 1 else 0
let bobSigned    = if(sigVerify(tx.bodyBytes, tx.proofs[1], bobPubKey)) then 1 else 0
let cooperSigned = if(sigVerify(tx.bodyBytes, tx.proofs[2], cooperPubKey)) then 1 else 0
#суммируем все корректные подписи и проверяем их количество
aliceSigned + bobSigned + cooperSigned >= 2

Register spravovaný tokenmi (TCR)

Mnohé blockchainové platformy čelia problému toxických aktív. Napríklad akákoľvek adresa, ktorá platí poplatok, môže na Waves vytvoriť aktívum.

Register spravovaný tokenmi (TCR), generovaný držiteľmi tokenov, pomáha riešiť problém ochrany používateľov a samotného blockchainu pred toxickými aktívami.

Aby držiteľ mohol hlasovať za pridanie konkrétneho tokenu do zoznamu, musí vložiť vklad rovnajúci sa jeho podielu na celkovej ponuke vydaných tokenov. Token je zaradený do registra, ak zaň hlasuje väčšina jeho držiteľov.

V našom príklade umožňujeme používateľovi pridať token do zoznamu na posúdenie (počas obdobia „výzvy“) pomocou stavového kľúča key = asset_name iba v prípade, že aktuálna hodnota count = 0.

Používateľ musí mať tiež nenulový zostatok tohto tokenu vo svojej peňaženke. Potom sa začne hlasovacie obdobie, počas ktorého môže používateľ hlasovať za každé aktívum vo svojej peňaženke, ale iba raz, a to hodnotením od 1 do 10. Hlasy používateľov sú reprezentované kľúčmi vo formáte adresa_používateľa+ID_aktuality.

let asset = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
let addingStartHeight = 1000
let votingStartHeight = 2000
let votingEndHeight = 3000

#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)

#извлекаем адрес из пруфа транзакции
let address = addressFromPublicKey(tx.proofs[1])
match tx {
   case t: DataTransaction =>
       if(height > addingStartHeight)
       then(
           if(height < votingStartHeight)
           then(
               #adding
               #выясняем, есть ли этот ассет у этого адреса
               let hasTokens = assetBalance(address, asset) > 0
               size(t.data) == 1
                     #убеждаемся, что этот ассет еще не был добавлен
                     && !isDefined(getInteger(this, toBase58String(asset)))
                     #убеждаемся, что по ключу-ассету добавляется значение равное 0
                     && extract(getInteger(t.data, toBase58String(asset))) == 0
                     && hasTokens
           )
           else(
               if(height < votingEndHeight)
               then
               (
                   #voting
                   #узнаем текущее количество голосов за данный ассет и задаваемое количество
                   let currentAmount = extract(getInteger(this, toBase58String(asset)))
                   let newAmount = extract(getInteger(t.data, toBase58String(asset)))
                   let betString = toBase58String(address.bytes) + toBase58String(asset)

                   #убеждаемся, что этот адрес еще не голосовал за этот ассет
                   let noBetBefore = !isDefined(getInteger(this, betString))
                   let isBetCorrect = extract(getInteger(t.data, betString)) > 0
                       && extract(getInteger(t.data, betString)) <= 10
                  
                   #убеждаемся, что у голосующего есть необходимые токены
                   let hasTokens = assetBalance(address, asset) > 0
                   #проверяем корректность значений транзакции
                   size(t.data) == 2 && isDefined(getInteger(this, toBase58String(asset)))
                       && newAmount == currentAmount + 1
                       && noBetBefore && isBetCorrect && hasTokens
               )
               else false
           ) && sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1])
       )
       else false
   case _ => false
}

Poplatok za predplatné

V tomto príklade sa pozrieme na používanie inteligentných účtov na pravidelné platby za produkt alebo službu v stanovených intervaloch – „poplatok za predplatné“.
Ak používateľ poskytne inteligentnému účtu (prostredníctvom dôkazov o transakcii) ID TransferTransaction s požadovanou sumou prevedených prostriedkov, môže na účet zapísať stav {kľúč: adresa, hodnota: pravdivý}.

To bude znamenať, že používateľ potvrdí svoje predplatné produktu alebo služby. Po uplynutí platnosti predplatného môže ktorýkoľvek používateľ siete nastaviť hodnotu vedľa príslušného kľúča v stave. nepravdivý.

let subscriptionPeriod = 44000
let signature = tx.proofs[0]
let pk = tx.proofs[1]
let requiredAmount = 100000

#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)
match tx {
   case d: DataTransaction =>
     #извлекаем дату последнего платежа
     let lastPaymentHeight = extract(getInteger(this, d.data[0].key + "_lastPayment"))
     size(d.data) == 1 && d.data[0].value == "false" && lastPaymentHeight + subscriptionPeriod < height
     ||
     (
       let address = d.data[0].key

       #извлекаем трансфер-транзакцию по ID, указанному в пруфах
       let ttx = transactionById(d.proofs[0])
       
       size(d.data) == 2
           && d.data[0].value == "true"
           && d.data[1].key == address + "_lastPayment"
           && match ttx {
             case purchase : TransferTransaction =>
               d.data[1].value == transactionHeightById(purchase.id)
               && toBase58String(purchase.sender.bytes) == address
               && purchase.amount == requiredAmount
               && purchase.recipient == this
               #убеждаемся, что ассет waves
               && !isDefined(purchase.assetId)
         case _ => false
       }
     )
   case _ => false
}

hlasovanie

Inteligentné účty sa dajú použiť na implementáciu hlasovania v blockchaine. Príkladom by mohlo byť hlasovanie o najlepšej správe ambasádora ako súčasť programu ambasádorov. Stav účtu sa používa ako platforma na zaznamenávanie hlasov pre konkrétnu možnosť.

V tomto príklade môžu hlasovať iba tí, ktorí si zakúpili špeciálne „hlasovacie“ tokeny. Účastník vopred odošle dátovú transakciu s párom (kľúč, hodnota) = (IdTransactionpurchase, IdTransactionbuy). Nastavenie inej hodnoty pre tento kľúč je zakázané. Dátový záznam je možné nastaviť iba raz pomocou ich adresy a možnosti hlasovania. Hlasovanie je možné iba počas určeného obdobia.

let asset = base58'8jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
let address = addressFromPublicKey(tx.proofs[1])
let votingStartHeight = 2000
let votingEndHeight = 3000

#извлекаем из транзакции адрес отправителя
let this = extract(tx.sender)
 match tx {
   case t: DataTransaction =>
     (height > votingStartHeight && height < votingEndHeight) &&
     #убеждаемся, что у транзакции правильная подпись
     sigVerify(tx.bodyBytes, tx.proofs[0], tx.proofs[1]) &&

     #проверяем, что пользователь отдает свой голос напротив своего адреса
     if (t.data[0].key == toBase58String(address.bytes))
     then (
       #извлекаем транзакцию перевод голосовательного токена из пруфов
       let purchaseTx = transactionById(t.proofs[7])
       
       match purchaseTx {
         case purchase : TransferTransaction =>
           let correctSender = purchase.sender == t.sender
           let correctAsset = purchase.assetId == asset
           let correctPrice = purchase.amount == 1
           let correctProof = extract(getBinary(this, toBase58String(purchase.id))) == t.id
           correctSender && correctAsset && correctPrice && correctProof
         case _ => false
       }
     )
     else
         size(t.data) == 1 && !isDefined(getBinary(this, t.data[0].key))
   case _ => false
}

Úpis

Zmenka je písomný záväzok, podľa ktorého je jedna strana povinná zaplatiť druhej strane pevnú sumu peňazí v čase požiadania alebo vo vopred určenom termíne.

V našom príklade používame inteligentný účet, ktorého dátum expirácie zodpovedá dátumu platby faktúry.

let expiration = 100000
let amount = 10
let asset =  base58'9jfD2JBLe23XtCCSQoTx5eAW5QCU6Mbxi3r78aNQLcNf'
  
let Bob = Address(base58'3NBVqYXrapgJP9atQccdBPAgJPwHDKkh6A8')
let Alice = Address(base58'3PNX6XwMeEXaaP1rf5MCk8weYeF7z2vJZBg')
  
match tx {
    case t: TransferTransaction =>
        (t.assetId == asset)&&      
        (t.amount == amount)&&       
        (t.sender == Bob)&&
        (t.recipient == Alice)&&
        (sigVerify(t.bodyBytes, t.proofs[0], t.senderPublicKey))&&
        (height >= expiration)
    case _ => false
}

záloha

Vklad je umiestnenie finančných prostriedkov v banke za určitých podmienok (doba splatnosti, úrok).
V našom príklade funguje inteligentný účet ako banka. Po určitom počte blokov, zodpovedajúcom lehote vkladu, môže používateľ vrátiť svoje peniaze s úrokom. Skript určí výšku bloku (finalHeight), po ktorej môže používateľ vybrať prostriedky z účtu.

heightUnit je počet blokov v jednej časovej jednotke (napr. mesiac, rok atď.). Najprv skontrolujeme prítomnosť záznamu s párom (key, value) = (initialTransferTransaction, futureDataTransaction). Potom musí používateľ odoslať TransferTransaction so správnymi informáciami o výške vkladu a úroku nahromadenom počas obdobia vkladu. Tieto informácie sa overia oproti pôvodnej TransferTransaction, ktorá je obsiahnutá v aktuálnom dôkaze TransferTransaction. depositDivisor je inverzná hodnota zlomku vkladu (ak je vklad prijatý vo výške 10 %, zlomok vkladu je 0,1 a depositDevisor = 1/0,1 = 10).

let this = extract(tx.sender)
let depositDivisor = 10
let heightUnit = 1000
let finalHeight = 100000
 match tx {
   case e : TransferTransaction =>
     #извлекаем высоту транзакции по ID транзакции в седьмом пруфе  
     let depositHeight = extract(transactionHeightById(e.proofs[7]))

     #извлекаем транзакцию депозита
     let purchaseTx = transactionById(e.proofs[7])    
     match purchaseTx {
       case deposit : TransferTransaction =>
         let correctSender = deposit.sender == e.sender
         #убеждаемся, что пользователь переводит себе корректную сумму депозита + проценты
         let correctAmount = deposit.amount + deposit.amount / depositDivisor * (height - depositHeight) / heightUnit == e.amount
         let correctProof = extract(getBinary(this, toBase58String(deposit.id))) == e.id
         correctSender && correctProof && correctAmount
       case _ => false
     }
     && finalHeight <= height 
 case _ => sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}

V treťom a zároveň poslednom článku tejto série preskúmame ďalšie prípady použitia inteligentných aktív vrátane zmrazenia a obmedzenia transakcií pre konkrétne adresy.

Zdroj: hab.com