Un Sistema de Comercio Local Sin Confianza
29 de mayo de 2019

Un Sistema de Comercio Local (LETS) está destinado a desarrollar la economía local y generalmente es utilizado por personas de una localidad en las cercanías entre sí. Para una breve descripción de LETS, consulte este enlace, que también describe una implementación de ErgoScript de un LETS gestionado por un comité. Llamamos a tal sistema gestionado o con permiso, ya que depende de un comité de miembros de confianza para inscribir nuevos miembros en el LETS.
Aquí describimos un LETS sin confianza, es decir, uno donde no se necesita un comité de gestión para la inscripción.
Descripción General
LETS involucra a varias partes que acuerdan usar alguna forma de "moneda local", generalmente vinculada a la moneda principal del país a una tasa de 1:1. Supongamos que nuestro LETS está basado en un país europeo donde la moneda es el Euro, y el intercambio se realiza en "Euros locales", que se consideran equivalentes a los Euros nacionales.
Cada usuario en LETS tiene una cuenta, que contiene el saldo LETS de ese usuario (en Euros Locales). Al unirse, cada usuario tiene un saldo de cero. El saldo se almacena en un libro mayor (posiblemente descentralizado). Una característica interesante de LETS es que un usuario con saldo cero también puede "retirar" dinero, pero solo para pagar a otro usuario de LETS. En cualquier momento, la suma de los saldos LETS de todos los usuarios es cero.
Como ejemplo, Alice con saldo cero desea comprar un litro de leche por 2 Euros de Bob, quien también es miembro de LETS con saldo cero. Ella transfiere 2 Euros de su cuenta a la de Bob, haciendo que su saldo sea -2 y el de Bob +2. Bob puede entonces transferir parte o la totalidad de su saldo a otro usuario de LETS a cambio de bienes o servicios.
LETS Sin Confianza
Dado que deseamos un LETS sin confianza, no podemos depender de ningún grupo de personas de confianza para admitir usuarios. Tenga en cuenta que aún tendremos un comité para realizar algunas tareas, como establecer los parámetros del LETS (moneda local, el número máximo de miembros, etc.) y consumir cualquier tarifa de inscripción.
Solo asumiremos un oráculo de precios de confianza que proporciona la tasa actual de euros a ergs identificada por algún id global (rateTokenID) y una caja singleton que contiene exactamente un token con este id. Una caja singleton, descrita aquí, es una caja que contiene un token singleton, es decir, un token con solo una cantidad en existencia. Esta caja también contiene la tasa de ergs a euros en cualquier período de tiempo dado. La tasa se actualiza gastando esta caja y creando otra caja singleton con la nueva tasa.
En cualquier momento, nuestro LETS está definido de manera única por una caja de token global que contiene algunos tokens de membresía con id letsTokenID. Esta caja define los parámetros del LETS, como la ubicación, la unidad de moneda, rateTokenID, etc. La caja de tokens se inicia inicialmente con, digamos, 10000 tokens de membresía. Los usuarios pueden gastar esta caja y crear sus cajas LETS individuales como salidas de la transacción, de modo que cada salida tenga exactamente un token de membresía y los tokens de membresía restantes se coloquen en una nueva caja de tokens creada.
Una caja LETS representa a un miembro de LETS y debe ser utilizada en cada transacción. Para simplificar, este artículo restringe todas las transacciones LETS a involucrar exactamente dos miembros, uno siendo el remitente y el otro el receptor, de modo que el remitente transfiere una cantidad positiva de la moneda LETS (euros locales) al receptor. Tal transacción consume las cajas del miembro y las recrea como salida con el saldo actualizado.
La Variante Básica
Para prevenir spam y ataques DDoS, requerimos al menos un número mínimo de ergs (minErgsToJoin) que se bloqueen en la caja del nuevo miembro creada. Los ergs se bloquearán hasta que se hayan minado al menos minWithdrawTime número de bloques. Se permite que una caja tenga un saldo LETS negativo hasta la cantidad que puede ser cubierta por los ergs bloqueados (usando la tasa en el momento del comercio).
// una tokenBox almacena los tokens de membresía y tiene este script
val tokenBox = OUTPUTS(0) // la primera salida también debe ser una tokenBox
// la primera salida contiene los tokens LETS restantes
def isLets(b:Box) = { // devuelve verdadero si b es una caja LETS
// Una caja LETS debe tener exactamente 1 token de membresía en tokens(0)
b.tokens(0)._1 == letsTokenID && b.tokens(0)._2 == 1 &&
blake2b256(b.propositionBytes) == memberBoxScriptHash &&
SELF.R4[Long].get == 0 && // iniciar la caja con saldo LETS cero
b.value >= minErgsToJoin && // la caja debe contener algunos ergs mínimos
b.R6[Long].get <= HEIGHT // almacenar la altura de creación en R6
}
// cuántas cajas LETS se crearon en la tx
val numLetsBoxes = OUTPUTS.filter({(b:Box) => isLets(b)}).size
// En la transacción, lo siguiente se preserva para la caja de tokens ...
tokenBox.tokens(0)._1 == SELF.tokens(0)._1 && // id del token
tokenBox.tokens(0)._2 == SELF.tokens(0)._2 - numLetsBoxes && // cantidad
tokenBox.propositionBytes == SELF.propositionBytes // script
La caja de un miembro de LETS está protegida por el script a continuación, cuyo hash memberBoxScriptHash se utiliza arriba. El script requiere exactamente un par (remitente, receptor) por transacción.
val validRateOracle = CONTEXT.dataInputs(0).tokens(0)._1 == rateTokenID
val rate = CONTEXT.dataInputs(0).R4[Int].get
val inBalance = SELF.R4[Long].get // saldo LETS de la entrada actual
val pubKey = SELF.R5[SigmaProp].get // propietario de la entrada actual
val createdAt = SELF.R6[Long].get // altura en la que se minó la entrada actual
val index = getVar[Int](0).get // índice de la salida correspondiente
val out = OUTPUTS(index)
val outBalance = out.R4[Long].get // saldo LETS de la salida
// Una caja LETS es aquella que tiene el mismo script que la caja actual
val isMemberBox = {(b:Box) => b.propositionBytes == SELF.propositionBytes}
val letsInputs = INPUTS.filter(isMemberBox) // todas las cajas de entrada LETS
val letsOutputs = OUTPUTS.filter(isMemberBox) // todas las cajas de salida LETS
// La entrada actual pertenece al receptor si su saldo LETS aumenta
// Puede haber algunos ergs en la caja de entrada del receptor. Necesitamos asegurarnos de que
// la caja de salida del receptor también contenga la misma cantidad de ergs que la entrada
val receiver = outBalance > inBalance && out.value == SELF.value
val getBalance = {(b:Box) => b.R4[Long].get} // devuelve el saldo LETS de una caja
val letsBalIn = letsInputs.map(getBalance).fold(0L, {(l:Long, r:Long) => l + r})
val letsBalOut = letsOutputs.map(getBalance).fold(0L, {(l:Long, r:Long) => l + r})
// la caja del remitente puede contener menos cantidad de ergs (el remitente puede retirar ergs siempre que
// cualquier saldo LETS negativo del remitente en la salida esté respaldado por suficientes ergs)
val correctErgs = out.value >= -outBalance * rate && (
out.value >= SELF.value || SELF.R6[Long].get + minWithdrawTime > HEIGHT
)
// para el receptor, no tocamos el saldo de ergs,
// ya que un receptor no está involucrado activamente en la transacción
inBalance != outBalance && // debe ocurrir alguna transacción; el saldo debe cambiar
SELF.tokens(0)._1 == letsTokenID && // la entrada actual tiene el token correcto
out.tokens(0)._1 == letsTokenID && // la salida correspondiente tiene el token correcto
validRateOracle && // el oráculo que proporciona la tasa tiene el "token de tasa" correcto
letsBalIn == letsBalOut && // el saldo total LETS se preserva en la transacción
letsInputs.size == 2 && letsOutputs.size == 2 && // solo dos entradas y salidas LETS
out.propositionBytes == SELF.propositionBytes && // out es una caja LETS ...
out.R5[SigmaProp].get == pubKey && // ... con la clave pública correcta
out.R6[Long].get == SELF.R6[Long].get && // ... y altura de creación
(receiver || // o la entrada actual pertenece al receptor ...
(pubKey && correctErgs) // ... o la salida tiene los ergs correctos y la tx tiene firma
)
La transacción que gasta una caja con el script anterior requiere:
- La suma del saldo LETS de las entradas y salidas se preserva
- Hay dos entradas LETS y dos salidas LETS
- Las claves públicas (almacenadas en R5) se preservan en la salida correspondiente
- La altura de creación (almacenada en R6) se preserva en la salida correspondiente
Decimos que alguna clave pública es el receptor si el saldo LETS de su salida es mayor que el de su entrada.
La última condición requiere que las cajas de entrada y salida pertenezcan al receptor (para que los ergs se preserven), o, en caso de que pertenezcan al remitente, se proporcione una firma y la salida esté respaldada por el número requerido de ergs si su saldo LETS es negativo. Además, requiere que el saldo de ergs del remitente no pueda reducirse hasta que se hayan minado al menos minWithdrawTime número de bloques después de que los ergs fueron bloqueados.
En comparación con el LETS gestionado, el sistema anterior tiene las siguientes diferencias:
- Sin registro de membresía: A diferencia del LETS gestionado, no almacenamos ninguna información de membresía aquí.
- Múltiples cajas: Una persona puede crear múltiples cajas de membresía, lo cual está permitido. Solo requerimos que cualquier saldo negativo esté respaldado por el número correspondiente de ergs bloqueados en ella.
LETS-1: Suma Cero, Colateral
Lo anterior es la variante básica, que llamamos LETS-1. Tiene las siguientes características:
- Tarifa de Inscripción Bloqueada por Tiempo: Para prevenir ataques de spam, un miembro debe pagar una cierta tarifa mínima en ergs en el momento de unirse. Esta tarifa es reembolsable pero solo después de un número predefinido de bloques.
- Suma Cero: La suma de los saldos LETS de todas las cajas de miembros es cero. Se permite que las cajas de miembros tengan un saldo negativo siempre que esté dentro de un cierto límite.
- Colateral: Para la salida del remitente, se utilizan ergs como colateral para cubrir el saldo LETS negativo a la tasa de cambio actual.
Las siguientes son algunas variaciones de LETS-1.
LETS-2: Suma Cero, Sin colateral
Esta es una ligera variación de LETS-1 como sigue:
- Tarifa de Inscripción No Reembolsable: Similar a LETS-1, se necesita una tarifa de inscripción para prevenir ataques de spam. Sin embargo, a diferencia de LETS-1, esta tarifa no es reembolsable y debe enviarse a algún comité de gestión predefinido.
- Suma Cero: Como en LETS-1.
LETS-3: Suma Positiva, Colateral
Las dos variantes anteriores requieren que el saldo total LETS sea siempre cero. Aquí consideramos un valor positivo para esta suma. En particular, esta variante tiene las siguientes propiedades:
- Tarifa de Inscripción Bloqueada por Tiempo: Como en LETS-1.
- Suma Positiva: El saldo LETS de cada miembro debe ser siempre no negativo. Esto asegura que la suma de los saldos LETS de todas las cajas de miembros sea positiva. El saldo LETS inicial se establece en un valor positivo basado en la tarifa de inscripción a la tasa actual, limitado a algún valor máximo.
- Colateral: Cualquier reducción en el saldo de ergs del remitente debe ir acompañada de una reducción del saldo LETS correspondiente a la tasa de cambio actual.
También podemos permitir aumentar el saldo LETS durante una transacción añadiendo la cantidad equivalente de ergs.
LETS-4: Suma Positiva, Sin colateral
Esto es similar a LETS-3 pero con algunas pequeñas variaciones:
- Tarifa de Inscripción No Reembolsable: Como en LETS-2
- Suma Positiva: Como en LETS-3
La siguiente tabla resume las variantes:
| Suma Cero | Suma Positiva | |
|---|---|---|
| Colateral | LETS-1 | LETS-3 |
| Sin colateral | LETS-2 | LETS-4 |
Consideramos transacciones LETS que involucran un solo par remitente-receptor. Modelos más avanzados pueden permitir múltiples remitentes y receptores, y no necesitan estar en pares.
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








