Decentralized Exchange Contracts on Ergo

Translation temporarily unavailable. Showing original English.
Denys Zadorozhnyi

31 de julio de 2020

Ergo has expressive smart contracts and transactional model which allows for an implementation of trustless DEX protocol, in which signed buy and sell orders can be put into the blockchain independently by buyers and sellers. An off-chain matching service can observe the Ergo blockchain, find matching orders, and submit the swap transaction without knowing any secrets. The matching can be incentivized by DEX reward paid as part of a swap transaction. Anyone who first discover the match of the two orders can create the swap transaction and get a reward in ERGs. Partial matching is supported, meaning that target (buy/sell) order can be executed partially, in which case a new "residual" order(box) has to be created in the same swap transaction. Any order can be canceled anytime by the "owner".

Sell order contract source.

Buy order contract source.

Partial matching

Both contracts have token price and DEX fee parameters encoded on a compilation. This allows us to check the "residual" order assets, ERGs for a buy order, and tokens for a sell order.

In the buy order contract, we search for a residual box, checking that it has correct parameters and assets.

// in case of partial matching new buy order box should be created with funds that are not matched in this tx
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
}

source

Then, we check that the following properties hold:

  • Value (ERGs) of the "residual" order box is the value of the current box(order) minus ERGs value of the tokens we're receiving in this swap transaction and minus the DEX fee for this swap transaction.
  • Only one "residual" order box is created in this swap transaction.
// ERGs paid for the bought tokens
val returnTokenValue = returnTokenAmount * tokenPrice
// branch for total matching (all ERGs are spent and correct amount of tokens is bought)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue && 
  returnBox.value >= fullSpread
// branch for partial matching, e.g. besides bought tokens we demand a new buy order with ERGs for 
// non-matched part of this order
val partialMatching = {
  val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
  foundResidualOrderBoxes.size == 1 && 
    foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue && 
    returnBox.value >= fullSpread
}

source

In the sell order contract, we search for a residual box, checking that it has correct parameters and assets.

// in case of partial matching new sell order box should be created with tokens that are not matched in this tx
// check that there is only one such box is made later in the code
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
}

source

Then, we check that the following properties hold:

  • The difference between the token amount in the current box(order) and the "residual" order box determines the amount of ERGs seller receives for the tokens "sold" in this swap transaction (soldTokenAmount * tokenPrice).
  • Value (ERGs) of the "residual" order box is the value of the current box(order) minus the DEX fee for this swap transaction.
  • Only one "residual" order box is created in this swap transaction.
// branch for partial matching, e.g. besides received ERGs we demand a new sell order with tokens for 
// non-matched part of this order
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
  }
}

source

Total matching

Both sell and buy orders can be executed in the swap transaction entirely. In this case, there is no requirement for the "residual" order box.
For this path, we check that the following properties hold.
For sell order:

  • ERGs amount seller receives in this swap transaction have to be equal to amount of tokens in the current order times token price.
    val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))
    source

For buy order:

  • Token value (token amount * token price, in ERGs) buyer receives in this swap transaction have to be equal to the value of the current box(order) minus DEX fee.
    val totalMatching = (SELF.value - expectedDexFee) == (returnTokenAmount * tokenPrice) && returnBox.value >= fullSpread
    source

Bid-Ask spread

Counter orders sorting check

The spread is the difference between the buy(bid) order price and sell(ask) order price. We want to make sure that if there is a spread, the "older" order gets it.
For this contract requires the counter orders (spending orders) have to be ordered by spread amount. So that ones with a bigger spread will be "consumed" first.
In buy order contract:

// check if this order should get the spread for a given counter order(height)
val spreadIsMine = { (counterOrderBoxHeight: Int) => 
// greater or equal since only a strict greater gives win in sell order contract
// Denys: we have to decide who gets the spread if height is equal, without any reason I chose buy order
counterOrderBoxHeight >= SELF.creationInfo._1 
}

// check that counter(sell) orders are sorted by spread in INPUTS
// so that the bigger(top) spread will be "consumed" first
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 
}
}

source

We also check the declared token price in the R5 register of the counter sell orders is in the correct range to prevent exploiting arithmetic overflow and other similar attacks.

In sell order contract:

// check if this order should get the spread for a given counter order(height)
val spreadIsMine = { (counterOrderBoxHeight: Int) => 
// strictly greater since equality gives win in buy order contract
// Denys: we have to decide who gets the spread if height is equal, without any reason I chose buy order
counterOrderBoxHeight > SELF.creationInfo._1 
}

// check that counter(buy) orders are sorted by spread in INPUTS
// so that the bigger(top) spread will be "consumed" first
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)
    // although buy order's DEX fee is not used here, we check if its positive as a part of 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 
}
}

source

We also check the declared token price in the R5 register, and DEX fee per token in R6 of the counter buy orders is in the correct range.

Spread calculation

To check that the current order gets its spread, we need to calculate it first. With counter orders sorted by the spread amount, we start to "consume" them in that order, decreasing the number of tokens left in this match.
In buy order contract:

// aggregated spread we get from all counter(sell) orders
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 is ours
      val spreadPerToken = tokenPrice - sellOrderTokenPrice
      val sellOrderSpread = spreadPerToken * tokenAmountFromThisOrder
      (returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread + sellOrderSpread)
    }
    else {
      // spread is not ours
      (returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread)
    }
  })._2
}

source

In sell order contract we need to relay on both token price and DEX fee amount to calculate how many tokens are in that buy order. Besides that, since we cannot deduce token amount "sold" in this swap transaction from the return box value we make spread calculation parametrized with concrete token amount that we will know later in the code:

// aggregated spread we get from all counter(buy) orders
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 is ours
      val spreadPerToken = buyOrderTokenPrice - tokenPrice
      val buyOrderSpread = spreadPerToken * tokenAmountInThisOrder
      (returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread + buyOrderSpread)
    }
    else {
      // spread is not ours
      (returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread)
    }
  })._2
}

source

Check received spread

With the spread amount determined, we need to check if the current order is indeed received the spread.
In buy order contract we check that it's included in return box value:

// branch for total matching (all ERGs are spent and correct amount of tokens is bought)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue && 
  returnBox.value >= fullSpread
// branch for partial matching, e.g. besides bought tokens we demand a new buy order with ERGs for 
// non-matched part of this order
val partialMatching = {
  val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
  foundResidualOrderBoxes.size == 1 && 
    foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue && 
    returnBox.value >= fullSpread
}

source

In the sell order contract, as soon as we know the token amount "sold" in this swap transaction, we check that return box value has the spread included.
In total matching case we use total token amount in the current order:

// branch for total matching (all tokens are sold and full amount ERGs received)
val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))

source

In partial matching case we know the amount of token "sold" from the residual order( val soldTokenAmount = selfTokenAmount - residualOrderTokenAmount) and check that the spread is included in the return box value:

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

source

Share post

Ergo Infrastructure DAO: Descentralizando la columna vertebral del ecosistema Ergo

Ergo Infrastructure DAO: Descentralizando la columna vertebral del ecosistema Ergo

La misión de Ergo siempre ha estado arraigada en la descentralización, no solo en la capa de consenso, sino en toda la pila.

Ergo Platform

13 de agosto de 2025

Mew Finance: Un Kit de Herramientas DeFi Divertido para el Ecosistema Ergo

Mew Finance: Un Kit de Herramientas DeFi Divertido para el Ecosistema Ergo

Mew Finance es un conjunto de aplicaciones descentralizadas en la Blockchain de Ergo.

Ergo Platform

12 de agosto de 2025

Lithos: Descentralizando la Minería con Pools On-Chain

Lithos: Descentralizando la Minería con Pools On-Chain

Lithos es un nuevo protocolo diseñado para reformar cómo funcionan los pools de minería al trasladarlos a la cadena, dando a los m.

Ergo Platform

24 de julio de 2025

Sigma 6.0: Un Ergo Más Inteligente y Flexible

Sigma 6.0: Un Ergo Más Inteligente y Flexible

Sigma 6.0 es una importante actualización propuesta para la blockchain de Ergo.

Ergo Platform

23 de julio de 2025

Dando forma al futuro de Rosen: Una llamada comunitaria sobre cinco propuestas clave del Tesoro

Dando forma al futuro de Rosen: Una llamada comunitaria sobre cinco propuestas clave del Tesoro

El cofundador de Rosen, Armeanio, ha presentado cinco nuevas propuestas al Tesoro de Rosen.

Ergo Platform

9 de julio de 2025

El UTXO Ampliado de Ergo y el Auge de la Inteligencia Económica Artificial

El UTXO Ampliado de Ergo y el Auge de la Inteligencia Económica Artificial

Una Visión Práctica para Agentes Económicos Autónomos Los agentes económicos autónomos en la blockchain de Ergo realizan trabajos.

Ergo Platform

12 de mayo de 2025

ErgoHACK X: Inteligencia Artificial en la Blockchain de Ergo

ErgoHACK X: Inteligencia Artificial en la Blockchain de Ergo

Celebrando una Década de Innovación Descentralizada ¡Únete al décimo aniversario de ErgoHACK y sé parte de la revolución de la IA .

Ergo Platform

10 de abril de 2025

Introduccion a Privacidad y Seguridad en la Blockchain

Introduccion a Privacidad y Seguridad en la Blockchain

Luego del primer whitepaper que apareció en Internet en el 2008, la tecnología blockchain evoluciono enormemente.

Ergo Platform

17 de febrero de 2022

Método híbrido de calcular costes de Ergo

Método híbrido de calcular costes de Ergo

Introducción Verificar la validez de los contratos inteligentes en una blockchain de Prueba de trabajo (PoW) tiene costos, tanto.

Ergo Platform (Translated by Darkkknight, original version will always prevail)

9 de febrero de 2022

Ergo: una respuesta a los fallos de la teoría monetaria moderna

Ergo: una respuesta a los fallos de la teoría monetaria moderna

En 2008, un grupo o persona desconocida lanzó un depósito de valor peer-to-peer y lo llamó Bitcoin.

Ergo Platform (Translated by Comet Community, original version will always prevail)

8 de febrero de 2022

Summit de Ergo : Evento para la privacidad

Summit de Ergo : Evento para la privacidad

Únase a nosotros del 17 al 23 de febrero de 2022 para este evento.

Ergo Foundation (translated by Daniu, original version will always prevail)

5 de febrero de 2022

Finanza descentralizada y privacidad opcional en Ergo

Finanza descentralizada y privacidad opcional en Ergo

Privacidad financial y blockchains públicas Bitcoin es una red de contabilidad distribuida pública a la que pueden acceder todos.

Ergo Platform (translated by Daniu, original version will always prevail)

1 de febrero de 2022

Alquiler por almacenamiento y el futuro de la minería

Alquiler por almacenamiento y el futuro de la minería

Terminología Storage Rent: Alquiler por almacenamiento (se entenderá más adelante) Introducción Los mineros son la capa de con.

Ergo Platform (translated by Daniu, original version will always prevail)

27 de enero de 2022

ErgoHack III: Construyendo la privacidad y seguridad del mañana

ErgoHack III: Construyendo la privacidad y seguridad del mañana

Ergo es una plataforma PoW de contratos inteligentes de código abierto basada en principios económicos de base.

Ergo Foundation (translated by Daniu, original version will always prevail)

20 de enero de 2022

Ergo & Blockchain: Escalabilidad y adopción

Ergo & Blockchain: Escalabilidad y adopción

En este episodio de la serie Ergo & Blockchain, veremos varios aspectos de escalabilidad y por qué son cruciales para la adopció.

Ergo Platform (translated by Daniu, original version will always prevail)

18 de enero de 2022

ErgoHack III Información para registrarse

ErgoHack III Información para registrarse

ErgoHack III tendrá lugar en Febrero 11-13, 2022 Registros abiertos hasta el 31 de Enero, 2022 Con el registro ya abierto, exi.

Ergo Foundation (translated by Daniu, original version will always prevail)

6 de enero de 2022

Ergo Rewards de minería: primera reducción de la emisión

Ergo Rewards de minería: primera reducción de la emisión

Las recompensas de la minería Ergo experimentaron su primera caída en el calendario de emisiones el 2 de enero de 2022 con el bl.

Ergo Platform (translated by Daniu, original version will always prevail)

4 de enero de 2022

¡Hola! soy nuevo, ¿por qué es Ergo un buen proyecto?

¡Hola! soy nuevo, ¿por qué es Ergo un buen proyecto?

¿Qué encontrarás en este artículo? Son numerosas las veces que un nuevo ergonauta en potencia entra a uno de los grupos en españo.

Daniu

1 de enero de 2022

Ergo Platform 2021: Resumen de este año

Ergo Platform 2021: Resumen de este año

A medida que el mundo intenta recuperarse de los efectos de Covid y las diferentes etapas de las restricciones de bloqueo, las c.

Ergo Platform (translated by Daniu, original version will always prevail)

30 de diciembre de 2021

Ergo y Blockchain: Tecnología e Innovación

Ergo y Blockchain: Tecnología e Innovación

La idea inicial detrás de Bitcoin se basó en la promesa de un comercio protegido de puntos centralizados de falla.

Ergo Platform (translated by Daniu, original version will always prevail)

28 de diciembre de 2021

Minería en Ergo: Herramientas de descentralización

Minería en Ergo: Herramientas de descentralización

Ergo es una cadena de bloques PoW (Prueba de trabajo) en el modelo de consenso llamado Autolykos.

Ergo Platform (translated by Daniu, original version will always prevail)

23 de diciembre de 2021