Decentralizované burzové zmluvy na Ergo

This page is machine-translated.
Denys Zadorozhnyi

31. júla 2020

Ergo má expresívne inteligentné zmluvy a transakčný model, ktorý umožňuje implementáciu bezdôvodného DEX protokolu, v ktorom môžu byť podpísané nákupné a predajné objednávky nezávisle vložené do blockchainu kupujúcimi a predávajúcimi. Služba na zladenie mimo reťazca môže sledovať blockchain Ergo, nájsť zladené objednávky a predložiť transakciu výmeny bez toho, aby poznala akékoľvek tajomstvá. Zladenie môže byť motivované odmenou DEX vyplatenou ako súčasť transakcie výmeny. Každý, kto prvý objaví zladenie dvoch objednávok, môže vytvoriť transakciu výmeny a získať odmenu v ERG. Podporuje sa čiastočné zladenie, čo znamená, že cieľová (nákupná/predajná) objednávka môže byť vykonaná čiastočne, pričom v takom prípade musí byť vytvorená nová "reziduálna" objednávka (box) v tej istej transakcii výmeny. Akúkoľvek objednávku môže kedykoľvek zrušiť "majiteľ".

Zmluva o predajnej objednávke zdroj.

Zmluva o nákupnej objednávke zdroj.

Čiastočné zladenie

Obe zmluvy majú parametre ceny tokenu a poplatku DEX zakódované pri kompilácii. To nám umožňuje skontrolovať aktíva "reziduálnej" objednávky, ERG pre nákupnú objednávku a tokeny pre predajnú objednávku.

V zmluve o nákupnej objednávke hľadáme reziduálny box, pričom kontrolujeme, že má správne parametre a aktíva.

// v prípade čiastočného zladenia by mal byť vytvorený nový box nákupnej objednávky s prostriedkami, ktoré nie sú zladené v tejto transakcii
val foundResidualOrderBoxes = OUTPUTS.filter { (b: Box) => 
  val tokenIdParamIsCorrect = b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == tokenId 
  val tokenPriceParamIsCorrect = b.R5[Long].isDefined && b.R5[Long].get == tokenPrice
  val dexFeePerTokenParamIsCorrect = b.R6[Long].isDefined && b.R6[Long].get == dexFeePerToken
  val contractParamsAreCorrect = tokenIdParamIsCorrect && 
    tokenPriceParamIsCorrect && dexFeePerTokenParamIsCorrect
  val referenceMe = b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id 
  val guardedByTheSameContract = b.propositionBytes == SELF.propositionBytes
  contractParamsAreCorrect && referenceMe && guardedByTheSameContract
}

zdroj

Potom kontrolujeme, že nasledujúce vlastnosti platia:

  • Hodnota (ERG) "reziduálnej" objednávky je hodnota aktuálneho boxu (objednávky) mínus hodnota ERG tokenov, ktoré dostávame v tejto transakcii výmeny a mínus poplatok DEX za túto transakciu výmeny.
  • V tejto transakcii výmeny je vytvorený iba jeden "reziduálny" objednávkový box.
// ERG zaplatené za zakúpené tokeny
val returnTokenValue = returnTokenAmount * tokenPrice
// vetva pre celkové zladenie (všetky ERG sú minulé a správne množstvo tokenov je zakúpené)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue && 
  returnBox.value >= fullSpread
// vetva pre čiastočné zladenie, napr. okrem zakúpených tokenov požadujeme novú nákupnú objednávku s ERG pre 
// nezladenú časť tejto objednávky
val partialMatching = {
  val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
  foundResidualOrderBoxes.size == 1 && 
    foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue && 
    returnBox.value >= fullSpread
}

zdroj

V zmluve o predajnej objednávke hľadáme reziduálny box, pričom kontrolujeme, že má správne parametre a aktíva.

// v prípade čiastočného zladenia by mal byť vytvorený nový box predajnej objednávky s tokenmi, ktoré nie sú zladené v tejto transakcii
// kontrola, že neskôr v kóde je vytvorený iba jeden taký box
val foundResidualOrderBoxes = OUTPUTS.filter { (b: Box) => 
  val tokenIdParamIsCorrect = b.R4[Coll[Byte]].isDefined && b.R4[Coll[Byte]].get == tokenId 
  val tokenPriceParamIsCorrect = b.R5[Long].isDefined && b.R5[Long].get == tokenPrice
  val dexFeePerTokenParamIsCorrect = b.R6[Long].isDefined && b.R6[Long].get == dexFeePerToken
  val contractParamsAreCorrect = tokenIdParamIsCorrect && 
    tokenPriceParamIsCorrect && 
    dexFeePerTokenParamIsCorrect
  val referenceMe = b.R7[Coll[Byte]].isDefined && b.R7[Coll[Byte]].get == SELF.id 
  val guardedByTheSameContract = b.propositionBytes == SELF.propositionBytes
  contractParamsAreCorrect && referenceMe && guardedByTheSameContract
}

zdroj

Potom kontrolujeme, že nasledujúce vlastnosti platia:

  • Rozdiel medzi množstvom tokenov v aktuálnom boxe (objednávke) a "reziduálnym" objednávkovým boxom určuje množstvo ERG, ktoré predávajúci dostáva za tokeny "predané" v tejto transakcii výmeny (soldTokenAmount * tokenPrice).
  • Hodnota (ERG) "reziduálnej" objednávky je hodnota aktuálneho boxu (objednávky) mínus poplatok DEX za túto transakciu výmeny.
  • V tejto transakcii výmeny je vytvorený iba jeden "reziduálny" objednávkový box.
// vetva pre čiastočné zladenie, napr. okrem prijatých ERG požadujeme novú predajnú objednávku s tokenmi pre 
// nezladenú časť tejto objednávky
val partialMatching = {
  foundResidualOrderBoxes.size == 1 && {
    val residualOrderBox = foundResidualOrderBoxes(0)
    val residualOrderTokenData = residualOrderBox.tokens(0)
    val residualOrderTokenAmount = residualOrderTokenData._2
    val soldTokenAmount = selfTokenAmount - residualOrderTokenAmount
    val soldTokenErgValue = soldTokenAmount * tokenPrice
    val expectedDexFee = dexFeePerToken * soldTokenAmount

    val residualOrderTokenId = residualOrderTokenData._1
    val tokenIdIsCorrect = residualOrderTokenId == tokenId

    val residualOrderValueIsCorrect = residualOrderBox.value == (SELF.value - expectedDexFee)
    val returnBoxValueIsCorrect = returnBox.value == soldTokenErgValue + fullSpread(soldTokenAmount)
    tokenIdIsCorrect && 
      soldTokenAmount >= 1 && 
      residualOrderValueIsCorrect && 
      returnBoxValueIsCorrect
  }
}

zdroj

Celkové zladenie

Obe predajné a nákupné objednávky môžu byť vykonané v transakcii výmeny úplne. V tomto prípade nie je požiadavka na "reziduálny" objednávkový box.
Pre túto cestu kontrolujeme, že nasledujúce vlastnosti platia.
Pre predajnú objednávku:

  • Množstvo ERG, ktoré predávajúci dostáva v tejto transakcii výmeny, musí byť rovné množstvu tokenov v aktuálnej objednávke krát cena tokenu.
    val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))
    zdroj

Pre nákupnú objednávku:

  • Hodnota tokenu (množstvo tokenov * cena tokenu, v ERG) ktorú kupujúci dostáva v tejto transakcii výmeny, musí byť rovná hodnote aktuálneho boxu (objednávky) mínus poplatok DEX.
    val totalMatching = (SELF.value - expectedDexFee) == (returnTokenAmount * tokenPrice) && returnBox.value >= fullSpread
    zdroj

Rozpätie ponuky a dopytu

Kontrola triedenia protichodných objednávok

Rozpätie je rozdiel medzi cenou nákupnej (ponukovej) objednávky a cenou predajnej (dopytovej) objednávky. Chceme sa uistiť, že ak existuje rozpätie, "staršia" objednávka ho dostane.
Pre túto zmluvu sa vyžaduje, aby protichodné objednávky (objednávky na výdavky) boli zoradené podľa výšky rozpätia. Tak, aby tie s väčším rozpätím boli "spotrebované" ako prvé.
V zmluve o nákupnej objednávke:

// kontrola, či by táto objednávka mala získať rozpätie pre danú protichodnú objednávku (výšku)
val spreadIsMine = { (counterOrderBoxHeight: Int) => 
// väčšie alebo rovné, pretože iba prísne väčšie dáva výhru v zmluve o predajnej objednávke
// Denys: musíme sa rozhodnúť, kto dostane rozpätie, ak je výška rovná, bez akéhokoľvek dôvodu som si vybral nákupnú objednávku
counterOrderBoxHeight >= SELF.creationInfo._1 
}

// kontrola, že protichodné (predajné) objednávky sú zoradené podľa rozpätia v INPUTS
// tak, aby väčšie (horné) rozpätie bolo "spotrebované" ako prvé
val sellOrderBoxesAreSortedBySpread = { (boxes: Coll[Box]) => 
boxes.size > 0 && {
  val alledgedlyTopSpread = if (spreadIsMine(boxes(0).creationInfo._1)) { 
    tokenPrice - boxes(0).R5[Long].getOrElse(0L)
  } else { 0L }
  boxes.fold((alledgedlyTopSpread, true), { (t: (Long, Boolean), box: Box) => 
    val prevSpread = t._1
    val isSorted = t._2
    val boxTokenPrice = box.R5[Long].getOrElse(0L)
    val boxTokenPriceIsCorrect = boxTokenPrice > 0 && boxTokenPrice <= tokenPrice
    val spread = if (spreadIsMine(box.creationInfo._1)) { 
      tokenPrice - boxTokenPrice 
    } else { 0L }
    (spread, isSorted && boxTokenPriceIsCorrect && spread <= prevSpread)
  })._2 
}
}

zdroj

Taktiež kontrolujeme, že deklarovaná cena tokenu v registri R5 protichodných predajných objednávok je v správnom rozsahu, aby sme predišli zneužívaniu aritmetického pretečenia a iným podobným útokom.

V zmluve o predajnej objednávke:

// kontrola, či by táto objednávka mala získať rozpätie pre danú protichodnú objednávku (výšku)
val spreadIsMine = { (counterOrderBoxHeight: Int) => 
// prísne väčšie, pretože rovnosť dáva výhru v zmluve o nákupnej objednávke
// Denys: musíme sa rozhodnúť, kto dostane rozpätie, ak je výška rovná, bez akéhokoľvek dôvodu som si vybral nákupnú objednávku
counterOrderBoxHeight > SELF.creationInfo._1 
}

// kontrola, že protichodné (nákupné) objednávky sú zoradené podľa rozpätia v INPUTS
// tak, aby väčšie (horné) rozpätie bolo "spotrebované" ako prvé
val buyOrderBoxesAreSortedBySpread = { (boxes: Coll[Box]) => 
boxes.size > 0 && {
  val alledgedlyTopSpread = if (spreadIsMine(boxes(0).creationInfo._1)) { 
    boxes(0).R5[Long].getOrElse(0L) - tokenPrice 
  } else { 0L }
  boxes.fold((alledgedlyTopSpread, true), { (t: (Long, Boolean), box: Box) => 
    val prevSpread = t._1
    val isSorted = t._2
    val boxTokenPrice = box.R5[Long].getOrElse(0L)
    // hoci poplatok DEX za nákupnú objednávku sa tu nepoužíva, kontrolujeme, či je kladný ako súčasť sanity check
    val boxDexFeePerToken = box.R6[Long].getOrElse(0L)
    val spread = if (spreadIsMine(box.creationInfo._1)) { boxTokenPrice - tokenPrice } else { 0L }
    (spread, isSorted && boxTokenPrice >= tokenPrice && boxDexFeePerToken > 0L && spread <= prevSpread)
  })._2 
}
}

zdroj

Taktiež kontrolujeme, že deklarovaná cena tokenu v registri R5 a poplatok DEX za token v R6 protichodných nákupných objednávok je v správnom rozsahu.

Výpočet rozpätia

Aby sme skontrolovali, že aktuálna objednávka dostane svoje rozpätie, musíme ho najprv vypočítať. S protichodnými objednávkami zoradenými podľa výšky rozpätia začíname "spotrebovávať" ich v tomto poradí, znižujúc počet tokenov, ktoré zostávajú v tejto zhode.
V zmluve o nákupnej objednávke:

// agregované rozpätie, ktoré dostávame zo všetkých protichodných (predajných) objednávok
val fullSpread = {
  spendingSellOrders.fold((returnTokenAmount, 0L), { (t: (Long, Long), sellOrder: Box) => 
    val returnTokensLeft = t._1
    val accumulatedFullSpread = t._2
    val sellOrderTokenPrice = sellOrder.R5[Long].get
    val sellOrderTokenAmount = sellOrder.tokens(0)._2
    val tokenAmountFromThisOrder = min(returnTokensLeft, sellOrderTokenAmount)
    if (spreadIsMine(sellOrder.creationInfo._1)) {
      // rozpätie je naše
      val spreadPerToken = tokenPrice - sellOrderTokenPrice
      val sellOrderSpread = spreadPerToken * tokenAmountFromThisOrder
      (returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread + sellOrderSpread)
    }
    else {
      // rozpätie nie je naše
      (returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread)
    }
  })._2
}

zdroj

V zmluve o predajnej objednávke sa musíme spoliehať na cenu tokenu a výšku poplatku DEX, aby sme vypočítali, koľko tokenov je v tejto nákupnej objednávke. Okrem toho, keďže nemôžeme odvodzovať množstvo tokenov "predaných" v tejto transakcii výmeny z hodnoty návratového boxu, robíme výpočet rozpätia parametrizovaný konkrétnym množstvom tokenov, ktoré budeme neskôr poznať v kóde:

// agregované rozpätie, ktoré dostávame zo všetkých protichodných (nákupných) objednávok
val fullSpread = { (tokenAmount: Long) =>
  spendingBuyOrders.fold((tokenAmount, 0L), { (t: (Long, Long), buyOrder: Box) => 
    val returnTokensLeft = t._1
    val accumulatedFullSpread = t._2
    val buyOrderTokenPrice = buyOrder.R5[Long].get
    val buyOrderDexFeePerToken = buyOrder.R6[Long].get
    val buyOrderTokenAmountCapacity = buyOrder.value / (buyOrderTokenPrice + buyOrderDexFeePerToken)
    val tokenAmountInThisOrder = min(returnTokensLeft, buyOrderTokenAmountCapacity)
    if (spreadIsMine(buyOrder.creationInfo._1)) {
      // rozpätie je naše
      val spreadPerToken = buyOrderTokenPrice - tokenPrice
      val buyOrderSpread = spreadPerToken * tokenAmountInThisOrder
      (returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread + buyOrderSpread)
    }
    else {
      // rozpätie nie je naše
      (returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread)
    }
  })._2
}

zdroj

Kontrola prijatého rozpätia

S určenou výškou rozpätia musíme skontrolovať, či aktuálna objednávka skutočne dostala rozpätie.
V zmluve o nákupnej objednávke kontrolujeme, že je zahrnuté v hodnote návratového boxu:

// vetva pre celkové zladenie (všetky ERG sú minulé a správne množstvo tokenov je zakúpené)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue && 
  returnBox.value >= fullSpread
// vetva pre čiastočné zladenie, napr. okrem zakúpených tokenov požadujeme novú nákupnú objednávku s ERG pre 
// nezladenú časť tejto objednávky
val partialMatching = {
  val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
  foundResidualOrderBoxes.size == 1 && 
    foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue && 
    returnBox.value >= fullSpread
}

zdroj

V zmluve o predajnej objednávke, akonáhle vieme množstvo tokenov "predaných" v tejto transakcii výmeny, kontrolujeme, že hodnota návratového boxu obsahuje rozpätie.
V prípade celkového zladenia používame celkové množstvo tokenov v aktuálnej objednávke:

// vetva pre celkové zladenie (všetky tokeny sú predané a plná suma ERG je prijatá)
val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))

zdroj

V prípade čiastočného zladenia vieme množstvo tokenov "predaných" z reziduálnej objednávky (val soldTokenAmount = selfTokenAmount - residualOrderTokenAmount) a kontrolujeme, že rozpätie je zahrnuté v hodnote návratového boxu:

val returnBoxValueIsCorrect = returnBox.value == soldTokenErgValue + fullSpread(soldTokenAmount)

zdroj

Share post

Ergo Infrastructure DAO: Decentralizácia chrbtice ekosystému Ergo

Ergo Infrastructure DAO: Decentralizácia chrbtice ekosystému Ergo

Misia Ergo bola vždy zakorenená v decentralizácii, nielen na konsenzuálnej vrstve, ale naprieč celým stackom.

Ergo Platform

13. augusta 2025

Mew Finance: Hravý DeFi nástroj pre ekosystém Ergo

Mew Finance: Hravý DeFi nástroj pre ekosystém Ergo

Mew Finance je decentralizovaná aplikácia na blockchainu Ergo.

Ergo Platform

12. augusta 2025

Lithos: Decentralizácia ťažby s on-chain poolmi

Lithos: Decentralizácia ťažby s on-chain poolmi

Lithos je nový protokol navrhnutý na prepracovanie fungovania ťažobných poolov presunutím ich na on-chain, čo dáva ťažiarom plnú k.

Ergo Platform

24. júla 2025

Sigma 6.0: Inteligentnejší, flexibilnejší Ergo

Sigma 6.0: Inteligentnejší, flexibilnejší Ergo

Sigma 6.0 je významná navrhovaná aktualizácia blockchainu Ergo.

Ergo Platform

23. júla 2025

Formovanie budúcnosti Rosen: Výzva komunity na päť kľúčových návrhov pokladnice

Formovanie budúcnosti Rosen: Výzva komunity na päť kľúčových návrhov pokladnice

Spoluzakladateľ Rosen, Armeanio, predložil päť nových návrhov pre Rosen Treasury.

Ergo Platform

9. júla 2025

Ergo's Extended UTXO a vzostup umelej ekonomickej inteligencie

Ergo's Extended UTXO a vzostup umelej ekonomickej inteligencie

Praktická vízia pre autonómne ekonomické agentov Autonómne ekonomické agenti na blockchaine Ergo vykonávajú užitočnú prácu v reál.

Ergo Platform

12. mája 2025

ErgoHACK X: Umelá inteligencia na Ergo blockchaine

ErgoHACK X: Umelá inteligencia na Ergo blockchaine

Oslavujeme desaťročie decentralizovanej inovácií Pridajte sa k 10.

Ergo Platform

10. apríla 2025

The Ergo Manifesto

The Ergo Manifesto

Ergo Manifesto dúfa vo vzdelanie a ukážku vízie, čo blockchain technológia môže dosiahnuť.

Ergo Foundation

26. apríla 2021