Kontrak Pertukaran Terdesentralisasi di Ergo

This page is machine-translated.
Denys Zadorozhnyi

31 Juli 2020

Ergo memiliki kontrak pintar yang ekspresif dan model transaksional yang memungkinkan implementasi protokol DEX tanpa kepercayaan, di mana pesanan beli dan jual yang ditandatangani dapat dimasukkan ke dalam blockchain secara independen oleh pembeli dan penjual. Layanan pencocokan off-chain dapat mengamati blockchain Ergo, menemukan pesanan yang cocok, dan mengirimkan transaksi swap tanpa mengetahui rahasia apa pun. Pencocokan dapat diinsentifkan dengan imbalan DEX yang dibayarkan sebagai bagian dari transaksi swap. Siapa pun yang pertama kali menemukan kecocokan dari dua pesanan dapat membuat transaksi swap dan mendapatkan imbalan dalam ERG. Pencocokan parsial didukung, yang berarti bahwa pesanan target (beli/jual) dapat dieksekusi sebagian, di mana dalam hal ini pesanan "residual" baru (kotak) harus dibuat dalam transaksi swap yang sama. Pesanan apa pun dapat dibatalkan kapan saja oleh "pemilik".

Kontrak pesanan jual sumber.

Kontrak pesanan beli sumber.

Pencocokan Parsial

Kedua kontrak memiliki harga token dan parameter biaya DEX yang dikodekan pada kompilasi. Ini memungkinkan kita untuk memeriksa aset pesanan "residual", ERG untuk pesanan beli, dan token untuk pesanan jual.

Dalam kontrak pesanan beli, kita mencari kotak residual, memeriksa bahwa ia memiliki parameter dan aset yang benar.

// dalam kasus pencocokan parsial, kotak pesanan beli baru harus dibuat dengan dana yang tidak dicocokkan dalam tx ini
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
}

sumber

Kemudian, kita memeriksa bahwa sifat berikut berlaku:

  • Nilai (ERG) dari kotak pesanan "residual" adalah nilai dari kotak saat ini (pesanan) dikurangi nilai ERG dari token yang kita terima dalam transaksi swap ini dan dikurangi biaya DEX untuk transaksi swap ini.
  • Hanya satu kotak pesanan "residual" yang dibuat dalam transaksi swap ini.
// ERG yang dibayarkan untuk token yang dibeli
val returnTokenValue = returnTokenAmount * tokenPrice
// cabang untuk pencocokan total (semua ERG dibelanjakan dan jumlah token yang benar dibeli)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue && 
  returnBox.value >= fullSpread
// cabang untuk pencocokan parsial, misalnya selain token yang dibeli, kita meminta pesanan beli baru dengan ERG untuk 
// bagian yang tidak dicocokkan dari pesanan ini
val partialMatching = {
  val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
  foundResidualOrderBoxes.size == 1 && 
    foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue && 
    returnBox.value >= fullSpread
}

sumber

Dalam kontrak pesanan jual, kita mencari kotak residual, memeriksa bahwa ia memiliki parameter dan aset yang benar.

// dalam kasus pencocokan parsial, kotak pesanan jual baru harus dibuat dengan token yang tidak dicocokkan dalam tx ini
// periksa bahwa hanya ada satu kotak seperti itu yang dibuat kemudian dalam kode
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
}

sumber

Kemudian, kita memeriksa bahwa sifat berikut berlaku:

  • Selisih antara jumlah token di kotak saat ini (pesanan) dan kotak pesanan "residual" menentukan jumlah ERG yang diterima penjual untuk token yang "dijual" dalam transaksi swap ini (soldTokenAmount * tokenPrice).
  • Nilai (ERG) dari kotak pesanan "residual" adalah nilai dari kotak saat ini (pesanan) dikurangi biaya DEX untuk transaksi swap ini.
  • Hanya satu kotak pesanan "residual" yang dibuat dalam transaksi swap ini.
// cabang untuk pencocokan parsial, misalnya selain ERG yang diterima, kita meminta pesanan jual baru dengan token untuk 
// bagian yang tidak dicocokkan dari pesanan ini
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
  }
}

sumber

Pencocokan Total

Baik pesanan jual maupun beli dapat dieksekusi sepenuhnya dalam transaksi swap. Dalam hal ini, tidak ada persyaratan untuk kotak pesanan "residual".
Untuk jalur ini, kita memeriksa bahwa sifat berikut berlaku.
Untuk pesanan jual:

  • Jumlah ERG yang diterima penjual dalam transaksi swap ini harus sama dengan jumlah token dalam pesanan saat ini dikalikan dengan harga token.
    val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))
    sumber

Untuk pesanan beli:

  • Nilai token (jumlah token * harga token, dalam ERG) yang diterima pembeli dalam transaksi swap ini harus sama dengan nilai kotak saat ini (pesanan) dikurangi biaya DEX.
    val totalMatching = (SELF.value - expectedDexFee) == (returnTokenAmount * tokenPrice) && returnBox.value >= fullSpread
    sumber

Spread Bid-Ask

Pemeriksaan pengurutan pesanan kontra

Spread adalah selisih antara harga pesanan beli (bid) dan harga pesanan jual (ask). Kami ingin memastikan bahwa jika ada spread, pesanan yang "lebih tua" mendapatkannya.
Untuk kontrak ini, pesanan kontra (pesanan pengeluaran) harus diurutkan berdasarkan jumlah spread. Sehingga yang memiliki spread lebih besar akan "dikonsumsi" terlebih dahulu.
Dalam kontrak pesanan beli:

// periksa apakah pesanan ini harus mendapatkan spread untuk pesanan kontra yang diberikan (tinggi)
val spreadIsMine = { (counterOrderBoxHeight: Int) => 
// lebih besar atau sama karena hanya yang lebih besar yang memberikan kemenangan dalam kontrak pesanan jual
// Denys: kita harus memutuskan siapa yang mendapatkan spread jika tinggi sama, tanpa alasan apapun saya memilih pesanan beli
counterOrderBoxHeight >= SELF.creationInfo._1 
}

// periksa bahwa pesanan kontra (jual) diurutkan berdasarkan spread di INPUTS
// sehingga spread yang lebih besar (atas) akan "dikonsumsi" terlebih dahulu
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 
}
}

sumber

Kami juga memeriksa bahwa harga token yang dinyatakan di register R5 dari pesanan jual kontra berada dalam rentang yang benar untuk mencegah eksploitasi overflow aritmatika dan serangan serupa lainnya.

Dalam kontrak pesanan jual:

// periksa apakah pesanan ini harus mendapatkan spread untuk pesanan kontra yang diberikan (tinggi)
val spreadIsMine = { (counterOrderBoxHeight: Int) => 
// lebih besar secara ketat karena kesetaraan memberikan kemenangan dalam kontrak pesanan beli
// Denys: kita harus memutuskan siapa yang mendapatkan spread jika tinggi sama, tanpa alasan apapun saya memilih pesanan beli
counterOrderBoxHeight > SELF.creationInfo._1 
}

// periksa bahwa pesanan kontra (beli) diurutkan berdasarkan spread di INPUTS
// sehingga spread yang lebih besar (atas) akan "dikonsumsi" terlebih dahulu
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)
    // meskipun biaya DEX pesanan beli tidak digunakan di sini, kami memeriksa apakah positif sebagai bagian dari pemeriksaan kesehatan
    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 
}
}

sumber

Kami juga memeriksa bahwa harga token yang dinyatakan di register R5, dan biaya DEX per token di R6 dari pesanan beli kontra berada dalam rentang yang benar.

Perhitungan Spread

Untuk memeriksa bahwa pesanan saat ini mendapatkan spreadnya, kita perlu menghitungnya terlebih dahulu. Dengan pesanan kontra yang diurutkan berdasarkan jumlah spread, kita mulai "mengonsumsi" mereka dalam urutan itu, mengurangi jumlah token yang tersisa dalam pencocokan ini.
Dalam kontrak pesanan beli:

// spread agregat yang kita dapatkan dari semua pesanan kontra (jual)
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)) {
      // spread adalah milik kita
      val spreadPerToken = tokenPrice - sellOrderTokenPrice
      val sellOrderSpread = spreadPerToken * tokenAmountFromThisOrder
      (returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread + sellOrderSpread)
    }
    else {
      // spread bukan milik kita
      (returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread)
    }
  })._2
}

sumber

Dalam kontrak pesanan jual, kita perlu mengandalkan baik harga token maupun jumlah biaya DEX untuk menghitung berapa banyak token yang ada dalam pesanan beli itu. Selain itu, karena kita tidak dapat menyimpulkan jumlah token yang "dijual" dalam transaksi swap ini dari nilai kotak pengembalian, kita membuat perhitungan spread yang diparameterkan dengan jumlah token konkret yang akan kita ketahui nanti dalam kode:

// spread agregat yang kita dapatkan dari semua pesanan kontra (beli)
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)) {
      // spread adalah milik kita
      val spreadPerToken = buyOrderTokenPrice - tokenPrice
      val buyOrderSpread = spreadPerToken * tokenAmountInThisOrder
      (returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread + buyOrderSpread)
    }
    else {
      // spread bukan milik kita
      (returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread)
    }
  })._2
}

sumber

Periksa spread yang diterima

Dengan jumlah spread yang ditentukan, kita perlu memeriksa apakah pesanan saat ini benar-benar menerima spread tersebut.
Dalam kontrak pesanan beli, kita memeriksa bahwa itu termasuk dalam nilai kotak pengembalian:

// cabang untuk pencocokan total (semua ERG dibelanjakan dan jumlah token yang benar dibeli)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue && 
  returnBox.value >= fullSpread
// cabang untuk pencocokan parsial, misalnya selain token yang dibeli, kita meminta pesanan beli baru dengan ERG untuk 
// bagian yang tidak dicocokkan dari pesanan ini
val partialMatching = {
  val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
  foundResidualOrderBoxes.size == 1 && 
    foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue && 
    returnBox.value >= fullSpread
}

sumber

Dalam kontrak pesanan jual, segera setelah kita mengetahui jumlah token yang "dijual" dalam transaksi swap ini, kita memeriksa bahwa nilai kotak pengembalian memiliki spread yang termasuk.
Dalam kasus pencocokan total, kita menggunakan jumlah total token dalam pesanan saat ini:

// cabang untuk pencocokan total (semua token terjual dan jumlah ERG penuh diterima)
val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))

sumber

Dalam kasus pencocokan parsial, kita mengetahui jumlah token yang "dijual" dari pesanan residual (val soldTokenAmount = selfTokenAmount - residualOrderTokenAmount) dan memeriksa bahwa spread termasuk dalam nilai kotak pengembalian:

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

sumber

Share post

Ergo Infrastructure DAO: Mendekentralisasi Tulang Punggung Ekosistem Ergo

Ergo Infrastructure DAO: Mendekentralisasi Tulang Punggung Ekosistem Ergo

Misi Ergo selalu berakar pada desentralisasi, tidak hanya di lapisan konsensus, tetapi di seluruh tumpukan.

Ergo Platform

13 Agustus 2025

Mew Finance: Alat DeFi yang Menyenangkan untuk Ekosistem Ergo

Mew Finance: Alat DeFi yang Menyenangkan untuk Ekosistem Ergo

Mew Finance adalah rangkaian aplikasi terdesentralisasi di Blockchain Ergo.

Ergo Platform

12 Agustus 2025

Lithos: Mendekentralisasi Penambangan dengan Kolam On-Chain

Lithos: Mendekentralisasi Penambangan dengan Kolam On-Chain

Lithos adalah protokol baru yang dirancang untuk merombak cara kerja kolam penambangan dengan memindahkannya ke on-chain, memberik.

Ergo Platform

24 Juli 2025

Sigma 6.0: Ergo yang Lebih Cerdas dan Fleksibel

Sigma 6.0: Ergo yang Lebih Cerdas dan Fleksibel

Sigma 6.0 adalah peningkatan besar yang diusulkan untuk blockchain Ergo.

Ergo Platform

23 Juli 2025

Membentuk Masa Depan Rosen: Panggilan Komunitas tentang Lima Proposal Kunci Perbendaharaan

Membentuk Masa Depan Rosen: Panggilan Komunitas tentang Lima Proposal Kunci Perbendaharaan

Pendiri bersama Rosen, Armeanio, telah mengajukan lima proposal baru ke Perbendaharaan Rosen.

Ergo Platform

9 Juli 2025

UTXO Diperluas Ergo dan Kebangkitan Kecerdasan Ekonomi Buatan

UTXO Diperluas Ergo dan Kebangkitan Kecerdasan Ekonomi Buatan

Visi Praktis untuk Agen Ekonomi Otonom Agen ekonomi otonom di blockchain Ergo melakukan pekerjaan berguna dalam ekonomi digital y.

Ergo Platform

12 Mei 2025

ErgoHACK X: Kecerdasan Buatan di Blockchain Ergo

ErgoHACK X: Kecerdasan Buatan di Blockchain Ergo

Merayakan Satu Dekade Inovasi Terdesentralisasi Bergabunglah dengan ulang tahun ke-10 ErgoHACK dan jadilah yang terdepan dalam rev.

Ergo Platform

10 April 2025