Система торговли на местной бирже без доверия
29 мая 2019 г.

Система торговли на местной бирже (LETS) направлена на развитие местной экономики и обычно используется людьми, находящимися в непосредственной близости друг к другу. Для краткого обзора LETS смотрите эту ссылку, которая также описывает реализацию ErgoScript системы LETS, управляемой комитетом. Мы называем такую систему управляемой или разрешенной, так как она зависит от комитета доверенных членов для зачисления новых участников в LETS.
Здесь мы описываем систему LETS без доверия, т.е. такую, для которой не требуется управленческий комитет для зачисления.
Обзор
LETS включает несколько сторон, которые согласны использовать какую-либо форму "местной валюты", обычно привязанной к основной валюте страны по курсу 1:1. Предположим, что наш LETS основан в европейской стране, где валютой являются евро, и обмен происходит в "местных евро", которые считаются эквивалентными национальным евро.
Каждый пользователь в LETS имеет счет, который содержит баланс LETS этого пользователя (в местных евро). При вступлении каждый пользователь имеет баланс ноль. Баланс хранится в (возможно, децентрализованном) реестре. Интересной особенностью LETS является то, что пользователь с нулевым балансом также может "снять" деньги, но только для оплаты другому пользователю LETS. В любое время сумма балансов LETS всех пользователей равна нулю.
В качестве примера, Алиса с нулевым балансом хочет купить один литр молока за 2 евро у Боба, который также является членом LETS с нулевым балансом. Она переводит 2 евро со своего счета на счет Боба, делая свой баланс -2, а баланс Боба +2. Затем Боб может перевести часть или весь свой баланс другому пользователю LETS в обмен на товары или услуги.
LETS без доверия
Поскольку мы желаем систему LETS без доверия, мы не можем полагаться на какую-либо доверенную группу людей для допуска пользователей. Обратите внимание, что у нас все равно будет комитет для выполнения некоторых задач, таких как установка параметров LETS (местная валюта, максимальное количество участников и т.д.) и потребление любых вступительных взносов.
Мы будем предполагать только доверенный ценовой оракул, который дает текущий курс евро к эргам, идентифицированный некоторым глобальным id (rateTokenID), и синглтон-бокс, содержащий ровно один токен с этим id. Синглтон-бокс, описанный здесь, это бокс, содержащий синглтон-токен, т.е. токен, существующий только в одном экземпляре. Этот бокс также содержит курс эргов к евро в любой данный период времени. Курс обновляется путем расходования этого бокса и создания другого синглтон-бокса с новым курсом.
В любой момент наш LETS уникально определяется глобальным токен-боксом, который содержит некоторые токены членства с id letsTokenID. Этот бокс определяет параметры LETS, такие как местоположение, единица валюты, rateTokenID и т.д. Токен-бокс изначально начинается с, скажем, 10000 токенов членства. Пользователи могут расходовать этот бокс и создавать свои индивидуальные LETS боксы в качестве выходов транзакции, так что каждый такой выход имеет ровно один токен членства, а оставшиеся токены членства помещаются в вновь созданный токен-бокс.
LETS бокс представляет собой члена LETS и должен использоваться в каждой транзакции. Для простоты, эта статья ограничивает все транзакции LETS участием ровно двух членов, один из которых является отправителем, а другой - получателем, так что отправитель переводит некоторую положительную сумму LETS валюты (местных евро) получателю. Такая транзакция расходует боксы членов и воссоздает их как выход с обновленным балансом.
Основной вариант
Чтобы предотвратить спам и DDoS-атаки, мы требуем, чтобы в новом созданном боксе члена было заблокировано как минимум некоторое минимальное количество эргов (minErgsToJoin). Эрги будут заблокированы до тех пор, пока не будет добыто как минимум minWithdrawTime количество блоков. Бокс может иметь отрицательный баланс LETS до суммы, которая может быть покрыта заблокированными эргами (с использованием курса на момент торговли).
// a tokenBox stores the membership tokens and has this script
val tokenBox = OUTPUTS(0) // the first output must also be a tokenBox
// first output contains remaining LETS tokens
def isLets(b:Box) = { // returns true if b is a LETS box
// A LETS box must have exactly 1 membership token in tokens(0)
b.tokens(0)._1 == letsTokenID && b.tokens(0)._2 == 1 &&
blake2b256(b.propositionBytes) == memberBoxScriptHash &&
SELF.R4[Long].get == 0 && // start the box with zero LETS balance
b.value >= minErgsToJoin && // the box must contain some minimum ergs
b.R6[Long].get <= HEIGHT // store the creation height in R6
}
// how many lets boxes creared in the tx
val numLetsBoxes = OUTPUTS.filter({(b:Box) => isLets(b)}).size
// In the transaction following is preserved for the token box ...
tokenBox.tokens(0)._1 == SELF.tokens(0)._1 && // token id
tokenBox.tokens(0)._2 == SELF.tokens(0)._2 - numLetsBoxes && // quantity
tokenBox.propositionBytes == SELF.propositionBytes // script
Бокс члена LETS защищен скриптом ниже, хэш которого memberBoxScriptHash используется выше. Скрипт требует ровно одну пару (отправитель, получатель) на транзакцию.
val validRateOracle = CONTEXT.dataInputs(0).tokens(0)._1 == rateTokenID
val rate = CONTEXT.dataInputs(0).R4[Int].get
val inBalance = SELF.R4[Long].get // LETS balance of current input
val pubKey = SELF.R5[SigmaProp].get // owner of the current input
val createdAt = SELF.R6[Long].get // height at which current input was mined
val index = getVar[Int](0).get // index of the corresponding output
val out = OUTPUTS(index)
val outBalance = out.R4[Long].get // LETS balance of the output
// A LETS box is one that has the same script as the current box
val isMemberBox = {(b:Box) => b.propositionBytes == SELF.propositionBytes}
val letsInputs = INPUTS.filter(isMemberBox) // all LETS input boxes
val letsOutputs = OUTPUTS.filter(isMemberBox) // all LETS output boxes
// The current input belongs to the receiver if its LETS balance increases
// There may be some ergs in receiver's input box. We need to ensure that
// the receiver's output box also contains the same amount of ergs as input
val receiver = outBalance > inBalance && out.value == SELF.value
val getBalance = {(b:Box) => b.R4[Long].get} // returns LETS balance of a box
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})
// sender box can contain less amount of ergs (sender may withdraw ergs provided
// that any negative LETS balance of sender in out is backed by sufficient ergs)
val correctErgs = out.value >= -outBalance * rate && (
out.value >= SELF.value || SELF.R6[Long].get + minWithdrawTime > HEIGHT
)
// for the receiver, we don't touch the erg balance,
// since a receiver is not actively involved in the transaction
inBalance != outBalance && // some transaction should occur; balance must change
SELF.tokens(0)._1 == letsTokenID && // the current input has the right token
out.tokens(0)._1 == letsTokenID && // corresponding output has the right token
validRateOracle && // oracle providing rate has the correct "rate token"
letsBalIn == letsBalOut && // total LETS balance is preserved in the transaction
letsInputs.size == 2 && letsOutputs.size == 2 && // only two LETS inputs, outputs
out.propositionBytes == SELF.propositionBytes && // out is a LETS box ...
out.R5[SigmaProp].get == pubKey && // ... with the right pub key
out.R6[Long].get == SELF.R6[Long].get && // ... and creation height
(receiver || // either current input belongs to receiver ...
(pubKey && correctErgs) // ... or out has correct ergs and tx has signature
)
Транзакция, расходующая бокс с вышеуказанным скриптом, требует:
- Сумма баланса LETS входов и выходов сохраняется
- Есть два LETS входа и два LETS выхода
- Публичные ключи (хранящиеся в R5) сохраняются в соответствующем выходе
- Высота создания (хранящаяся в R6) сохраняется в соответствующем выходе
Мы говорим, что некоторый публичный ключ является получателем, если баланс LETS его выхода выше, чем у его входа.
Последнее условие требует, чтобы либо входные и выходные боксы принадлежали получателю (чтобы эрги сохранялись), либо, в случае, если они принадлежат отправителю, была предоставлена подпись, и выход обеспечивался необходимым количеством эргов, если его баланс LETS отрицательный. Более того, это требует, чтобы баланс эргов отправителя не мог быть уменьшен, пока не будет добыто как минимум minWithdrawTime количество блоков после блокировки эргов.
По сравнению с управляемым LETS, вышеуказанная система имеет следующие отличия:
- Нет записи о членстве: В отличие от управляемого LETS, мы не храним здесь никакой информации о членстве.
- Несколько боксов: Человек может создать несколько боксов членства, что разрешено. Мы только требуем, чтобы любой отрицательный баланс был обеспечен соответствующим количеством заблокированных эргов в нем.
LETS-1: Нулевая сумма, Коллатераль
Вышеописанный вариант является основным, который мы называем LETS-1. Он имеет следующие особенности:
- Время-заблокированный вступительный взнос: Чтобы предотвратить спам-атаки, член должен заплатить определенный минимальный взнос в эргах в момент вступления. Этот взнос подлежит возврату, но только после предопределенного количества блоков.
- Нулевая сумма: Сумма балансов LETS всех боксов членов равна нулю. Боксы членов могут иметь отрицательный баланс, пока он находится в пределах определенного лимита.
- Коллатераль: Для выхода отправителя эрги используются в качестве коллатераля для покрытия отрицательного баланса LETS по текущему обменному курсу.
Следующие являются некоторыми вариациями LETS-1.
LETS-2: Нулевая сумма, Без коллатераля
Это небольшая вариация LETS-1 следующим образом:
- Невозвратный вступительный взнос: Подобно LETS-1, требуется вступительный взнос для предотвращения спам-атак. Однако, в отличие от LETS-1, этот взнос невозвратный и должен быть отправлен какому-то предопределенному управленческому комитету.
- Нулевая сумма: Как и в LETS-1.
LETS-3: Положительная сумма, Коллатераль
Вышеуказанные два варианта требуют, чтобы общий баланс LETS всегда был равен нулю. Здесь мы рассматриваем положительное значение для этой суммы. В частности, этот вариант имеет следующие свойства:
- Время-заблокированный вступительный взнос: Как в LETS-1.
- Положительная сумма: Баланс LETS каждого члена всегда должен быть неотрицательным. Это гарантирует, что сумма балансов LETS всех боксов членов положительна. Начальный баланс LETS устанавливается на положительное значение на основе вступительного взноса по текущему курсу, ограниченному некоторым максимальным значением.
- Коллатераль: Любое уменьшение баланса эргов отправителя должно сопровождаться уменьшением соответствующего баланса LETS по текущему обменному курсу.
Мы также можем разрешить пополнение баланса LETS во время транзакции, добавляя эквивалентное количество эргов.
LETS-4: Положительная сумма, Без коллатераля
Это похоже на LETS-3, но с некоторыми небольшими вариациями:
- Невозвратный вступительный взнос: Как в LETS-2
- Положительная сумма: Как в LETS-3
Следующая таблица резюмирует варианты:
| Нулевая сумма | Положительная сумма | |
|---|---|---|
| Коллатераль | LETS-1 | LETS-3 |
| Без коллатераля | LETS-2 | LETS-4 |
Мы рассматривали транзакции LETS, включающие пару отправитель-получатель. Более сложные модели могут позволить несколько отправителей и получателей и не обязательно должны быть в парах.
Share post
13 августа 2025 г.
9 июля 2025 г.
12 мая 2025 г.






