Waves смарт-шоттарын қолдану: аукциондардан бонустық бағдарламаларға дейін

Waves смарт-шоттарын қолдану: аукциондардан бонустық бағдарламаларға дейін

Блокчейн көбінесе криптовалюталармен ғана байланысты, бірақ DLT технологиясын қолдану аясы әлдеқайда кең. Блокчейнді қолданудың ең перспективалы бағыттарының бірі - автоматты түрде орындалатын және оған кірген тараптардың сенімін қажет етпейтін смарт келісімшарт.

RIDE – смарт келісімшарттарға арналған тіл

Waves смарт келісімшарттар үшін арнайы тілді әзірледі – RIDE. Оның толық құжаттамасы орналасқан осында. Ал міне - осы тақырып бойынша мақала Хабрда.

RIDE келісімі предикат болып табылады және нәтиже ретінде «шын» немесе «жалған» мәнін қайтарады. Тиісінше, транзакция блокчейнге жазылады немесе қабылданбайды. Смарт келісімшарт көрсетілген шарттарды орындауға толық кепілдік береді. RIDE-де келісім-шарт бойынша транзакцияларды жасау қазіргі уақытта мүмкін емес.

Бүгінгі таңда Waves смарт келісімшарттарының екі түрі бар: смарт тіркелгілер және смарт активтер. Смарт тіркелгі - бұл қарапайым пайдаланушы тіркелгісі, бірақ ол үшін барлық транзакцияларды басқаратын сценарий орнатылған. Смарт тіркелгі сценарийі келесідей болуы мүмкін, мысалы:

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

tx өңделетін транзакция болып табылады, біз үлгіні сәйкестендіру механизмін тек аудару транзакциясы болмаса ғана пайдалануға рұқсат етеміз. RIDE ішіндегі үлгі сәйкестігі транзакция түрін тексеру үшін пайдаланылады. Барлық бар тіркелгілерді смарт тіркелгі сценарийінде өңдеуге болады транзакция түрлері.

Сценарий сонымен қатар айнымалы мәндерді жариялай алады, шарттарды толық тексеру үшін «if-then-else» конструкцияларын және басқа әдістерді пайдалана алады. Келісімшарттардың дәлелденетін толықтығы мен күрделілігі (құны) болуын қамтамасыз ету үшін, келісім-шартты орындау басталғанға дейін болжауға болады, RIDE жүйесінде циклдар немесе өту мәлімдемелері жоқ.

Waves шоттарының басқа ерекшеліктеріне «күйдің» болуы, яғни шоттың күйі жатады. Деректер транзакцияларын (DataTransaction) пайдаланып тіркелгі күйіне жұптардың шексіз санын (кілт, мән) жаза аласыз. Бұл ақпаратты кейін REST API арқылы да, тікелей смарт келісімшартта да өңдеуге болады.

Әрбір транзакцияда қатысушының қолы, қажетті транзакцияның идентификаторы және т.б. енгізуге болатын дәлелдер массиві болуы мүмкін.

RIDE арқылы жұмыс істеу ЖЕРДЕ келісім-шарттың құрастырылған көрінісін көруге (егер ол құрастырылған болса), жаңа тіркелгілерді жасауға және оған сценарийлерді орнатуға, сондай-ақ пәрмен жолы арқылы транзакцияларды жіберуге мүмкіндік береді.

Толық цикл үшін, соның ішінде тіркелгі жасау, оған смарт келісім-шарт орнату және транзакцияларды жіберу, сіз REST API интерфейсімен (мысалы, C#, C, Java, JavaScript, Python, Rust, Elixir) өзара әрекеттесу үшін кітапхананы пайдалана аласыз. . IDE-мен жұмыс істеуді бастау үшін ЖАҢА түймесін басыңыз.

Смарт келісімшарттарды пайдалану мүмкіндіктері кең: транзакцияларға тыйым салудан белгілі мекенжайларға («қара тізім») бастап күрделі dApp-қа дейін.

Енді бизнесте смарт келісімшарттарды қолданудың нақты мысалдарын қарастырайық: аукциондарды өткізу, сақтандыру және адалдық бағдарламаларын жасау кезінде.

Аукциондар

Аукционды сәтті өткізудің шарттарының бірі – ашықтық: қатысушылар сауда-саттықты өзгерту мүмкін еместігіне сенімді болуы керек. Бұған блокчейннің арқасында қол жеткізуге болады, мұнда барлық ставкалар және олардың жасалған уақыты туралы өзгермейтін деректер барлық қатысушыларға қолжетімді болады.

Waves блокчейнінде өтінімдерді DataTransaction арқылы аукцион шотының күйінде жазуға болады.

Сондай-ақ, блок нөмірлерін пайдаланып аукционның басталу және аяқталу уақытын орнатуға болады: Waves блокчейніндегі блоктарды құру жиілігі шамамен тең 60 секунд.

1. Ағылшындық өсетін баға аукционы

Ағылшындық аукционға қатысушылар бір-бірімен бәсекелесе отырып, ұсыныстарды орналастырады. Әрбір жаңа ставка алдыңғысынан асып кетуі керек. Аукцион соңғы бағадан асатын қатысушылар болмаған кезде аяқталады. Бұл жағдайда ең жоғары баға ұсынған қатысушы көрсетілген соманы ұсынуы керек.

Сондай-ақ сатушы лоттың ең төменгі бағасын белгілейтін аукциондық опция бар, ал соңғы баға одан жоғары болуы керек. Әйтпесе, лот сатылмай қалады.

Бұл мысалда біз аукцион үшін арнайы жасалған есептік жазбамен жұмыс істеп жатырмыз. Аукцион ұзақтығы – 3000 блок, ал лоттың бастапқы бағасы – 0,001 ТОЛҚЫН. Қатысушы «бағасы» кілті және өз ұсынысының мәні бар DataTransaction жіберу арқылы өтінімді орналастыра алады.

Жаңа өтінімнің бағасы осы кілттің ағымдағы бағасынан жоғары болуы керек және қатысушының шотында кемінде [new_bid + комиссия] белгілері болуы керек. Сауда-саттыққа қатысушының мекен-жайы DataTransaction жолындағы «жіберуші» жолында жазылуы керек және ағымдағы өтінім блогының биіктігі аукцион мерзімі ішінде болуы керек.

Егер аукционның соңында қатысушы ең жоғары бағаны белгілесе, ол көрсетілген баға мен валюта жұбы бойынша сәйкес лот бойынша төлеу үшін Exchange Transaction жібере алады.

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. «Барлығы төленетін» аукцион

«Барлық төлеу» – аукционда лотты кім ұтқанына қарамастан, барлық қатысушылар ұсынысты төлейді. Әрбір жаңа қатысушы ұсынысты төлейді, ал максималды ұсыныс жасаған қатысушы лотты жеңеді.

Біздің мысалда аукционның әрбір қатысушысы 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 смарт активтерді аударған сайын, олар сізге N/taxDivisor көлемінде FeeCoin береді (оны сізден 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
}

Кэшбэк және адалдық бағдарламалары

Кэшбэк – сатып алушы өнімге немесе қызметке жұмсалған соманың бір бөлігін қайтарып алатын адалдық бағдарламасының түрі.

Бұл істі смарт тіркелгіні пайдаланып жүзеге асырған кезде, біз сақтандыру жағдайындағыдай дәлелдемелерді тексеруіміз керек. Қосарланған шығындарды болдырмау үшін пайдаланушы кэшбэкті алмас бұрын (кілт, мән) = (purchaseTransactionId, cashbackTransactionId) арқылы DataTransaction жіберуі керек.

Біз сондай-ақ DataTransaction арқылы бар кілттерге тыйым салуды орнатуымыз керек. cashbackDivisor – кэшбэк үлесіне бөлінген бірлік. Анау. егер кэшбэк үлесі 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)
}

Атомдық своп

Атомдық своп пайдаланушыларға биржаның көмегінсіз активтерді айырбастауға мүмкіндік береді. Атомдық своп кезінде мәміленің екі қатысушысы да оны белгілі бір уақыт ішінде растауы қажет.

Егер қатысушылардың кем дегенде біреуі мәміле үшін бөлінген уақыт ішінде мәмілені дұрыс растауды ұсынбаса, мәміле жойылады және айырбастау болмайды.

Біздің мысалда біз келесі смарт тіркелгі сценарийін қолданамыз:

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

пікір қалдыру