Kontrak Pertukaran Terdesentralisasi di Ergo
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
}
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
}
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
}
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
}
}
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
}
}
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
}
}
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
}
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
}
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
}
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))
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)
Share post
13 Agustus 2025
9 Juli 2025






