Um Sistema de Troca Local de Comércio Sem Confiança
29 de maio de 2019

Um Sistema de Troca Local (LETS) tem como objetivo desenvolver a economia local e é geralmente utilizado por pessoas de uma localidade nas proximidades umas das outras. Para uma visão geral do LETS, veja este link, que também descreve uma implementação do ErgoScript de um LETS gerido por um comitê. Chamamos esse sistema de gerido ou com permissão, uma vez que depende de um comitê de membros confiáveis para inscrever novos membros no LETS.
Aqui descrevemos um LETS sem confiança, ou seja, um onde não há necessidade de um comitê de gestão para a inscrição.
Visão Geral
O LETS envolve várias partes que concordam em usar alguma forma de "moeda local", geralmente atrelada à moeda principal do país em uma taxa de 1:1. Suponha que nosso LETS esteja baseado em um país europeu onde a moeda é o Euro, e a troca é feita em "Euros locais", que são considerados equivalentes aos Euros nacionais.
Cada usuário no LETS tem uma conta, que contém o saldo do LETS desse usuário (em Euros Locais). Ao ingressar, cada usuário tem um saldo de zero. O saldo é armazenado em um livro-razão (possivelmente descentralizado). Uma característica interessante do LETS é que um usuário com saldo zero também pode "retirar" dinheiro, mas apenas para pagar outro usuário do LETS. A qualquer momento, a soma dos saldos do LETS de todos os usuários é zero.
Como exemplo, Alice, com saldo zero, deseja comprar um litro de leite por 2 Euros de Bob, que também é membro do LETS com saldo zero. Ela transfere 2 Euros de sua conta para a de Bob, fazendo seu saldo -2 e o de Bob +2. Bob pode então transferir parte ou todo o seu saldo para outro usuário do LETS em troca de bens ou serviços.
LETS Sem Confiança
Como desejamos um LETS sem confiança, não podemos depender de nenhum grupo confiável de pessoas para admitir usuários. Note que ainda teremos um comitê para realizar algumas tarefas, como definir os parâmetros do LETS (moeda local, número máximo de membros, etc.) e consumir qualquer taxa de adesão.
Assumiremos apenas um oráculo de preços confiável que fornece a taxa atual de euros para ergs identificada por algum id global (rateTokenID) e uma caixa singleton contendo exatamente um token com esse id. Uma caixa singleton, descrita aqui, é uma caixa contendo um token singleton, ou seja, um token com apenas uma quantidade em existência. Esta caixa também contém a taxa de ergs para euros em qualquer período de tempo. A taxa é atualizada gastando esta caixa e criando outra caixa singleton com a nova taxa.
Em qualquer momento, nosso LETS é definido de forma única por uma caixa de token global que contém alguns tokens de adesão com id letsTokenID. Esta caixa define os parâmetros do LETS, como a localização, a unidade monetária, rateTokenID, etc. A caixa de token é inicialmente iniciada com, digamos, 10000 tokens de adesão. Os usuários podem gastar esta caixa e criar suas caixas LETS individuais como saídas da transação, de modo que cada saída tenha exatamente um token de adesão e os tokens de adesão restantes sejam colocados em uma nova caixa de token criada.
Uma caixa LETS representa um membro do LETS e deve ser usada em cada transação. Para simplicidade, este artigo restringe todas as transações do LETS a envolver exatamente dois membros, um sendo o remetente e o outro o receptor, de modo que o remetente transfira uma quantidade positiva da moeda LETS (euros locais) para o receptor. Tal transação consome as caixas do membro e as recria como saída com o saldo atualizado.
A Variante Básica
Para prevenir spam e ataques DDoS, exigimos pelo menos um número mínimo de ergs (minErgsToJoin) a serem bloqueados na caixa do novo membro criada. Os ergs serão bloqueados até que pelo menos minWithdrawTime número de blocos tenham sido minerados. Uma caixa pode ter um saldo LETS negativo até o montante que pode ser coberto pelos ergs bloqueados (usando a taxa no momento da troca).
// uma tokenBox armazena os tokens de adesão e tem este script
val tokenBox = OUTPUTS(0) // a primeira saída também deve ser uma tokenBox
// a primeira saída contém os tokens LETS restantes
def isLets(b:Box) = { // retorna verdadeiro se b é uma caixa LETS
// Uma caixa LETS deve ter exatamente 1 token de adesão em tokens(0)
b.tokens(0)._1 == letsTokenID && b.tokens(0)._2 == 1 &&
blake2b256(b.propositionBytes) == memberBoxScriptHash &&
SELF.R4[Long].get == 0 && // iniciar a caixa com saldo LETS zero
b.value >= minErgsToJoin && // a caixa deve conter alguns ergs mínimos
b.R6[Long].get <= HEIGHT // armazenar a altura de criação em R6
}
// quantas caixas lets foram criadas na tx
val numLetsBoxes = OUTPUTS.filter({(b:Box) => isLets(b)}).size
// Na transação, o seguinte é preservado para a caixa de token ...
tokenBox.tokens(0)._1 == SELF.tokens(0)._1 && // id do token
tokenBox.tokens(0)._2 == SELF.tokens(0)._2 - numLetsBoxes && // quantidade
tokenBox.propositionBytes == SELF.propositionBytes // script
A caixa de um membro do LETS é protegida pelo script abaixo, cujo hash memberBoxScriptHash é usado acima. O script requer exatamente um par (remetente, receptor) por transação.
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 da entrada atual
val pubKey = SELF.R5[SigmaProp].get // proprietário da entrada atual
val createdAt = SELF.R6[Long].get // altura em que a entrada atual foi minerada
val index = getVar[Int](0).get // índice da saída correspondente
val out = OUTPUTS(index)
val outBalance = out.R4[Long].get // saldo LETS da saída
// Uma caixa LETS é aquela que tem o mesmo script que a caixa atual
val isMemberBox = {(b:Box) => b.propositionBytes == SELF.propositionBytes}
val letsInputs = INPUTS.filter(isMemberBox) // todas as caixas de entrada LETS
val letsOutputs = OUTPUTS.filter(isMemberBox) // todas as caixas de saída LETS
// A entrada atual pertence ao receptor se seu saldo LETS aumentar
// Pode haver alguns ergs na caixa de entrada do receptor. Precisamos garantir que
// a caixa de saída do receptor também contenha a mesma quantidade de ergs que a entrada
val receiver = outBalance > inBalance && out.value == SELF.value
val getBalance = {(b:Box) => b.R4[Long].get} // retorna saldo LETS de uma caixa
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})
// a caixa do remetente pode conter menos quantidade de ergs (o remetente pode retirar ergs desde que
// qualquer saldo LETS negativo do remetente na saída seja respaldado por ergs suficientes)
val correctErgs = out.value >= -outBalance * rate && (
out.value >= SELF.value || SELF.R6[Long].get + minWithdrawTime > HEIGHT
)
// para o receptor, não tocamos no saldo de ergs,
// uma vez que um receptor não está ativamente envolvido na transação
inBalance != outBalance && // alguma transação deve ocorrer; o saldo deve mudar
SELF.tokens(0)._1 == letsTokenID && // a entrada atual tem o token correto
out.tokens(0)._1 == letsTokenID && // a saída correspondente tem o token correto
validRateOracle && // o oráculo que fornece a taxa tem o "token de taxa" correto
letsBalIn == letsBalOut && // o saldo total do LETS é preservado na transação
letsInputs.size == 2 && letsOutputs.size == 2 && // apenas duas entradas e saídas LETS
out.propositionBytes == SELF.propositionBytes && // out é uma caixa LETS ...
out.R5[SigmaProp].get == pubKey && // ... com a chave pública correta
out.R6[Long].get == SELF.R6[Long].get && // ... e altura de criação
(receiver || // ou a entrada atual pertence ao receptor ...
(pubKey && correctErgs) // ... ou a saída tem ergs corretos e a tx tem assinatura
)
A transação que gasta uma caixa com o script acima requer:
- A soma do saldo LETS das entradas e saídas é preservada
- Existem duas entradas LETS e duas saídas LETS
- As chaves públicas (armazenadas em R5) são preservadas na saída correspondente
- A altura de criação (armazenada em R6) é preservada na saída correspondente
Dizemos que alguma chave pública é o receptor se o saldo LETS de sua saída for maior que o de sua entrada.
A última condição requer que ou as caixas de entrada e saída pertençam ao receptor (para que os ergs sejam preservados), ou, caso pertençam ao remetente, uma assinatura seja fornecida e a saída seja respaldada pelo número necessário de ergs se seu saldo LETS for negativo. Além disso, requer que o saldo de ergs do remetente não possa ser reduzido até que pelo menos minWithdrawTime número de blocos tenham sido minerados após os ergs terem sido bloqueados.
Comparado ao LETS gerido, o sistema acima tem as seguintes diferenças:
- Sem registro de adesão: Ao contrário do LETS gerido, não armazenamos nenhuma informação de adesão aqui.
- Múltiplas caixas: Uma pessoa pode criar várias caixas de adesão, o que é permitido. Exigimos apenas que qualquer saldo negativo seja respaldado pelo número correspondente de ergs bloqueados nela.
LETS-1: Soma Zero, Colateral
O acima é a variante básica, que chamamos de LETS-1. Tem as seguintes características:
- Taxa de Adesão Bloqueada por Tempo: Para prevenir ataques de spam, um membro deve pagar uma certa taxa mínima em ergs no momento da adesão. Esta taxa é reembolsável, mas apenas após um número pré-definido de blocos.
- Soma Zero: A soma dos saldos LETS de todas as caixas de membros é zero. As caixas de membros podem ter um saldo negativo desde que esteja dentro de um certo limite.
- Colateral: Para a saída do remetente, os ergs são usados como colateral para cobrir o saldo LETS negativo na taxa de câmbio atual.
As seguintes são algumas variações do LETS-1.
LETS-2: Soma Zero, Sem colateral
Esta é uma leve variação do LETS-1 da seguinte forma:
- Taxa de adesão não reembolsável: Semelhante ao LETS-1, uma taxa de adesão é necessária para prevenir ataques de spam. No entanto, ao contrário do LETS-1, esta taxa não é reembolsável e deve ser enviada a algum comitê de gestão pré-definido.
- Soma Zero: Como no LETS-1.
LETS-3: Soma Positiva, Colateral
As duas variantes acima exigem que o saldo total do LETS seja sempre zero. Aqui consideramos um valor positivo para essa soma. Em particular, esta variante tem as seguintes propriedades:
- Taxa de Adesão Bloqueada por Tempo: Como no LETS-1.
- Soma Positiva: O saldo LETS de cada membro deve ser sempre não negativo. Isso garante que a soma dos saldos LETS de todas as caixas de membros seja positiva. O saldo LETS inicial é definido como um valor positivo com base na taxa de adesão na taxa atual, limitado a algum valor máximo.
- Colateral: Qualquer redução no saldo de ergs do remetente deve ser acompanhada por uma redução do saldo LETS correspondente na taxa de câmbio atual.
Também podemos permitir aumentar o saldo LETS durante uma transação adicionando a quantidade equivalente de ergs.
LETS-4: Soma Positiva, Sem colateral
Isso é semelhante ao LETS-3, mas com algumas pequenas variações:
- Taxa de Adesão Não Reembolsável: Como no LETS-2
- Soma Positiva: Como no LETS-3
A tabela a seguir resume as variantes:
| Soma Zero | Soma Positiva | |
|---|---|---|
| Colateral | LETS-1 | LETS-3 |
| Sem colateral | LETS-2 | LETS-4 |
Consideramos transações LETS envolvendo um único par remetente-receptor. Modelos mais avançados podem permitir múltiplos remetentes e receptores, e não precisam estar em pares.
Share post
13 de agosto de 2025
12 de agosto de 2025
9 de julho de 2025
12 de maio de 2025

7 de abril de 2022

8 de março de 2022


















