Contratos de Intercambio Descentralizado en Ergo
31 de julio de 2020

Ergo tiene contratos inteligentes expresivos y un modelo transaccional que permite la implementación de un protocolo DEX sin confianza, en el que las órdenes de compra y venta firmadas pueden ser introducidas en la blockchain de forma independiente por compradores y vendedores. Un servicio de emparejamiento fuera de la cadena puede observar la blockchain de Ergo, encontrar órdenes coincidentes y enviar la transacción de intercambio sin conocer ningún secreto. El emparejamiento puede ser incentivado por una recompensa DEX pagada como parte de una transacción de intercambio. Cualquiera que descubra primero el emparejamiento de las dos órdenes puede crear la transacción de intercambio y obtener una recompensa en ERGs. Se admite el emparejamiento parcial, lo que significa que la orden objetivo (compra/venta) puede ejecutarse parcialmente, en cuyo caso se debe crear una nueva orden "residual" (caja) en la misma transacción de intercambio. Cualquier orden puede ser cancelada en cualquier momento por el "propietario".
Contrato de orden de venta fuente.
Contrato de orden de compra fuente.
Emparejamiento parcial
Ambos contratos tienen parámetros de precio de token y tarifa DEX codificados en una compilación. Esto nos permite verificar los activos de la orden "residual", ERGs para una orden de compra y tokens para una orden de venta.
En el contrato de orden de compra, buscamos una caja residual, verificando que tiene los parámetros y activos correctos.
// en caso de emparejamiento parcial, se debe crear una nueva caja de orden de compra con fondos que no están emparejados en esta transacción
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
}
Luego, verificamos que se cumplan las siguientes propiedades:
- El valor (ERGs) de la caja de orden "residual" es el valor de la caja actual (orden) menos el valor en ERGs de los tokens que estamos recibiendo en esta transacción de intercambio y menos la tarifa DEX para esta transacción de intercambio.
- Solo se crea una caja de orden "residual" en esta transacción de intercambio.
// ERGs pagados por los tokens comprados
val returnTokenValue = returnTokenAmount * tokenPrice
// rama para emparejamiento total (todos los ERGs se gastan y se compra la cantidad correcta de tokens)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue &&
returnBox.value >= fullSpread
// rama para emparejamiento parcial, por ejemplo, además de los tokens comprados, exigimos una nueva orden de compra con ERGs para
// la parte no emparejada de esta orden
val partialMatching = {
val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
foundResidualOrderBoxes.size == 1 &&
foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue &&
returnBox.value >= fullSpread
}
En el contrato de orden de venta, buscamos una caja residual, verificando que tiene los parámetros y activos correctos.
// en caso de emparejamiento parcial, se debe crear una nueva caja de orden de venta con tokens que no están emparejados en esta transacción
// verificar que solo se crea una caja de este tipo más adelante en el código
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
}
Luego, verificamos que se cumplan las siguientes propiedades:
- La diferencia entre la cantidad de tokens en la caja actual (orden) y la caja de orden "residual" determina la cantidad de ERGs que el vendedor recibe por los tokens "vendidos" en esta transacción de intercambio (
soldTokenAmount * tokenPrice). - El valor (ERGs) de la caja de orden "residual" es el valor de la caja actual (orden) menos la tarifa DEX para esta transacción de intercambio.
- Solo se crea una caja de orden "residual" en esta transacción de intercambio.
// rama para emparejamiento parcial, por ejemplo, además de los ERGs recibidos, exigimos una nueva orden de venta con tokens para
// la parte no emparejada de esta orden
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
}
}
Emparejamiento total
Tanto las órdenes de venta como las de compra pueden ejecutarse en la transacción de intercambio en su totalidad. En este caso, no hay requisito para la caja de orden "residual".
Para este camino, verificamos que se cumplan las siguientes propiedades.
Para la orden de venta:
- La cantidad de ERGs que el vendedor recibe en esta transacción de intercambio debe ser igual a la cantidad de tokens en la orden actual multiplicada por el precio del token.
val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))
fuente
Para la orden de compra:
- El valor del token (cantidad de token * precio del token, en ERGs) que el comprador recibe en esta transacción de intercambio debe ser igual al valor de la caja actual (orden) menos la tarifa DEX.
val totalMatching = (SELF.value - expectedDexFee) == (returnTokenAmount * tokenPrice) && returnBox.value >= fullSpread
fuente
Diferencial de oferta-demanda
Verificación de orden de contrapartida
El diferencial es la diferencia entre el precio de la orden de compra (oferta) y el precio de la orden de venta (demanda). Queremos asegurarnos de que si hay un diferencial, la orden "más antigua" lo obtenga.
Para este contrato, se requiere que las órdenes de contrapartida (órdenes de gasto) estén ordenadas por la cantidad de diferencial. De modo que las que tienen un mayor diferencial se "consuman" primero.
En el contrato de orden de compra:
// verificar si esta orden debe obtener el diferencial para una orden de contrapartida dada (altura)
val spreadIsMine = { (counterOrderBoxHeight: Int) =>
// mayor o igual ya que solo un mayor estricto da la victoria en el contrato de orden de venta
// Denys: tenemos que decidir quién obtiene el diferencial si la altura es igual, sin ninguna razón elegí la orden de compra
counterOrderBoxHeight >= SELF.creationInfo._1
}
// verificar que las órdenes de contrapartida (venta) están ordenadas por diferencial en INPUTS
// para que el diferencial más grande (superior) se "consuma" primero
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
}
}
También verificamos que el precio de token declarado en el registro R5 de las órdenes de venta de contrapartida esté en el rango correcto para prevenir la explotación de desbordamientos aritméticos y otros ataques similares.
En el contrato de orden de venta:
// verificar si esta orden debe obtener el diferencial para una orden de contrapartida dada (altura)
val spreadIsMine = { (counterOrderBoxHeight: Int) =>
// estrictamente mayor ya que la igualdad da la victoria en el contrato de orden de compra
// Denys: tenemos que decidir quién obtiene el diferencial si la altura es igual, sin ninguna razón elegí la orden de compra
counterOrderBoxHeight > SELF.creationInfo._1
}
// verificar que las órdenes de contrapartida (compra) están ordenadas por diferencial en INPUTS
// para que el diferencial más grande (superior) se "consuma" primero
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)
// aunque la tarifa DEX de la orden de compra no se utiliza aquí, verificamos si es positiva como parte de la verificación de cordura
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
}
}
También verificamos que el precio de token declarado en el registro R5, y la tarifa DEX por token en R6 de las órdenes de compra de contrapartida esté en el rango correcto.
Cálculo del diferencial
Para verificar que la orden actual obtenga su diferencial, primero necesitamos calcularlo. Con las órdenes de contrapartida ordenadas por la cantidad de diferencial, comenzamos a "consumirlas" en ese orden, disminuyendo el número de tokens restantes en este emparejamiento.
En el contrato de orden de compra:
// diferencial agregado que obtenemos de todas las órdenes de contrapartida (venta)
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)) {
// el diferencial es nuestro
val spreadPerToken = tokenPrice - sellOrderTokenPrice
val sellOrderSpread = spreadPerToken * tokenAmountFromThisOrder
(returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread + sellOrderSpread)
}
else {
// el diferencial no es nuestro
(returnTokensLeft - tokenAmountFromThisOrder, accumulatedFullSpread)
}
})._2
}
En el contrato de orden de venta, necesitamos depender tanto del precio del token como de la cantidad de tarifa DEX para calcular cuántos tokens hay en esa orden de compra. Además de eso, dado que no podemos deducir la cantidad de tokens "vendidos" en esta transacción de intercambio del valor de la caja de retorno, hacemos el cálculo del diferencial parametrizado con la cantidad concreta de tokens que conoceremos más adelante en el código:
// diferencial agregado que obtenemos de todas las órdenes de contrapartida (compra)
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)) {
// el diferencial es nuestro
val spreadPerToken = buyOrderTokenPrice - tokenPrice
val buyOrderSpread = spreadPerToken * tokenAmountInThisOrder
(returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread + buyOrderSpread)
}
else {
// el diferencial no es nuestro
(returnTokensLeft - tokenAmountInThisOrder, accumulatedFullSpread)
}
})._2
}
Verificación del diferencial recibido
Con la cantidad de diferencial determinada, necesitamos verificar si la orden actual ha recibido efectivamente el diferencial.
En el contrato de orden de compra, verificamos que esté incluido en el valor de la caja de retorno:
// rama para emparejamiento total (todos los ERGs se gastan y se compra la cantidad correcta de tokens)
val totalMatching = (SELF.value - expectedDexFee) == returnTokenValue &&
returnBox.value >= fullSpread
// rama para emparejamiento parcial, por ejemplo, además de los tokens comprados, exigimos una nueva orden de compra con ERGs para
// la parte no emparejada de esta orden
val partialMatching = {
val correctResidualOrderBoxValue = (SELF.value - returnTokenValue - expectedDexFee)
foundResidualOrderBoxes.size == 1 &&
foundResidualOrderBoxes(0).value == correctResidualOrderBoxValue &&
returnBox.value >= fullSpread
}
En el contrato de orden de venta, tan pronto como sepamos la cantidad de tokens "vendidos" en esta transacción de intercambio, verificamos que el valor de la caja de retorno tenga incluido el diferencial.
En el caso de emparejamiento total, utilizamos la cantidad total de tokens en la orden actual:
// rama para emparejamiento total (todos los tokens se venden y se reciben la cantidad total de ERGs)
val totalMatching = (returnBox.value == selfTokenAmount * tokenPrice + fullSpread(selfTokenAmount))
En el caso de emparejamiento parcial, sabemos la cantidad de tokens "vendidos" de la orden residual (val soldTokenAmount = selfTokenAmount - residualOrderTokenAmount) y verificamos que el diferencial esté incluido en el valor de la caja de retorno:
val returnBoxValueIsCorrect = returnBox.value == soldTokenErgValue + fullSpread(soldTokenAmount)
Share post
13 de agosto de 2025
12 de agosto de 2025
9 de julio de 2025
12 de mayo de 2025

9 de febrero de 2022

8 de febrero de 2022

5 de febrero de 2022

1 de febrero de 2022

27 de enero de 2022

20 de enero de 2022

18 de enero de 2022

6 de enero de 2022

4 de enero de 2022

30 de diciembre de 2021

28 de diciembre de 2021

23 de diciembre de 2021








