Lokalny system wymiany handlowej na bazie Ergo
22 kwietnia 2019

Lokalny system wymiany handlowej (LETS) to lokalne stowarzyszenie wzajemnego kredytu, w którym członkowie mogą indywidualnie tworzyć wspólną
walutę kredytową, a wszystkie transakcje w systemie są zapisywane w wspólnym rejestrze.
Na przykład, załóżmy, że Alicja z zerowym saldem chce kupić litr surowego mleka od Boba.
Najpierw ustalają cenę, na przykład, załóżmy, że cena wynosi około 2 Euro (ponieważ Alicja i Bob
żyją w Irlandii). Po zapisaniu transakcji w rejestrze, saldo Alicji staje się -2 (minus
dwa) Euro, a saldo Boba staje się 2 Euro. Następnie Bob może wydać swoje 2 Euro, na przykład, na
piwo domowej roboty od Charliego. Często takie systemy nakładają ograniczenia na ujemne salda, a czasami
wręcz na dodatnie, aby promować wymianę w społeczności.
Historycznie, takie systemy zyskiwały popularność w czasach kryzysu. Pierwszy system został założony przez Michaela Lintona w
towarzystwie kanadyjskim, które utknęło w depresji w 1981 roku. Lokalne systemy wymiany handlowej były niezwykle popularne w
czasie wielkiej depresji argentyńskiej w latach 1998-2002. Większość grup LETS liczy od 50 do 250 członków, z papierowymi notami kredytowymi i
rejestrem prowadzonym przez rdzeń komitetu. Jednak papierowe waluty LETS wykazały pewne problemy, takie jak
fałszywe banknoty, możliwe nieodpowiednie zachowanie menedżerów systemu i tak dalej. Dlatego waluty LETS oparte na blockchainie mogą być lepsze
iż stare systemy. Więcej informacji na temat LETS można znaleźć w
"Ekologii pieniędzy" (autor: Richard Douthwaite) oraz
Wikipedia.
W tym artykule pokazujemy, jak LETS można zaimplementować na bazie Ergo. Z najlepszą naszą wiedzą, jest to
pierwsza implementacja tego rodzaju waluty społecznościowej na bazie blockchaina.
Nasza referencyjna implementacja
jest prosta i składa się z dwóch kontraktów, a mianowicie kontraktu zarządzającego i kontraktu wymiany.
Pomijamy wstępne informacje o Ergo, więc proszę przeczytać
artykuł ICO oraz
tutoriale ErgoScript (podstawowy i
zaawansowany) dla początkujących.
Niemniej jednak, zamierzamy wprowadzić kilka nowych terminów w kolejnych zdaniach.
Jeśli token jest wydawany w ilości równej jeden, nazywamy go tokenem singletonowym. Podobnie,
box, który zawiera token singletonowy, nazywamy boxem singletonowym.
Kontrakt zarządzający kontroluje box singletonowy, który przechowuje członków systemu LETS.
Kontrakt umożliwia dodawanie nowych członków w tempie jednego członka na jedną transakcję. Box
nie przechowuje członków, ale tylko mały skrót uwierzytelnionej struktury danych zbudowanej na podstawie
directory członków. Członek jest powiązany z tokenem singletonowym wydanym w transakcji, która
dodaje członka do directory. Transakcja tworzy nowy box członka, który zawiera
token singletonowy członka. Box członka jest chroniony przez kontrakt wymiany. Ponadto, nowo
utworzony box członka ma początkowe saldo zapisane w rejestrze R4, a saldo jest
równe zero w naszym przykładzie. Transakcja tworząca nowego członka musi dostarczyć dowód poprawności dla
transformacji directory.
Box kontraktu zarządzającego jest zazwyczaj kontrolowany przez komitet, a komitet może ewoluować w czasie. Aby to wspierać,
pozwalamy logice komitetu na zamieszkanie w rejestrze R5.
Na przykład, załóżmy, że nowy członek komitetu został dodany wraz z nowym członkiem LETS,
box kontraktu zarządzającego wymaga 2 z 3 podpisów, a box wyjściowy wymaga 3 z 4 podpisów.
W tym przypadku zawartość rejestru R5 w boxie wejściowym i wyjściowym różniłaby się.
Kod kontraktu zarządzającego w ErgoScript z komentarzami jest podany poniżej. Proszę zauważyć, że
"userContractHash" dotyczy hasha kontraktu wymiany.
val selfOut = OUTPUTS(0)
// Skrypt zarządzający
val managementScript = selfOut.R5[SigmaProp].get
// Szablon skryptu zarządzającego replikuje siebie, a skrypt zarządzający jest spełniony
val scriptCorrect = (selfOut.propositionBytes == SELF.propositionBytes) && managementScript
// Transakcja wydająca tworzy boxy dla directory, użytkownika, opłaty.
val outsSizeCorrect = OUTPUTS.size == 3
// Sprawdza, czy token etykiety zarządzającej replikuje siebie
val outTokenCorrect = (selfOut.tokens.size == 1) && (selfOut.tokens(0)._1 == letsToken)
// Sprawdza, czy nowy token został wydany, a jego ilość jest poprawna
// OUTPUTS(0) tokeny już sprawdzone przez outtokenCorrect
val issuedTokenId = INPUTS(0).id
val userOut = OUTPUTS(1)
val correctTokenAmounts =
(userOut.tokens.size == 1 &&
userOut.tokens(0)._1 == issuedTokenId &&
userOut.tokens(0)._2 == 1 &&
OUTPUTS(2).tokens.size == 0 &&
outTokenCorrect)
// Sprawdza, czy nowy użytkownik został utworzony z zerowym saldem
val zeroUserBalance = userOut.R4[Long].get == 0
val properUserScript = blake2b256(userOut.propositionBytes) == userContractHash
// Sprawdza, czy nowy identyfikator tokena został dodany do directory
val selfTree = SELF.R4[AvlTree].get
val toAdd: Coll[(Coll[Byte], Coll[Byte])] = Coll((issuedTokenId, Coll[Byte]()))
val proof = getVar[Coll[Byte]](1).get
val modifiedTree = selfTree.insert(toAdd, proof).get
val expectedTree = selfOut.R4[AvlTree].get
val treeCorrect = modifiedTree == expectedTree
correctTokenAmounts && scriptCorrect && treeCorrect && zeroUserBalance && properUserScript
correctTokenAmounts && scriptCorrect && treeCorrect && zeroUserBalance && properUserScript correctTokenAmounts && scriptCorrect && treeCorrect && zeroUserBalance && properUserScript
Skrypt kontraktu wymiany jest dość prosty i podany poniżej wraz z komentarzami opisującymi jego logikę. W
kontrakcie zakłada się, że transakcja wydająca dla boxa kontraktu wymiany otrzymuje co najmniej dwa wejścia,
a pierwsze dwa wejścia powinny być chronione przez skrypt kontraktu wymiany i zawierać tokeny członków LETS. Aby sprawdzić,
czy tokeny członków singletonów w wejściach rzeczywiście należą do systemu LETS, transakcja wydająca dostarcza box kontraktu zarządzającego jako pierwsze dane wejściowe tylko do odczytu, a także powinna dostarczyć dowód, że tokeny członków rzeczywiście należą do
directory uwierzytelnionego za pomocą rejestru R4 boxa kontraktu zarządzającego. "letsToken" w skrypcie dotyczy
tokena singletonowego boxa zarządzającego.
// Minimalne saldo dozwolone dla handlowca LETS
val minBalance = -20000
val lookupProof = getVar[Coll[Byte]](1).get
// Box tylko do odczytu, który zawiera directory członków LETS
val treeHolderBox = CONTEXT.dataInputs(0)
val properLetsToken = treeHolderBox.tokens(0)._1 == letsToken
val membersTree = treeHolderBox.R4[AvlTree].get
// Transakcja wydająca bierze dwa boxy członków LETS, którzy chcą dokonać transakcji,
// i zwraca boxy z zmodyfikowanymi saldami.
val participant0 = INPUTS(0)
val participant1 = INPUTS(1)
val participantOut0 = OUTPUTS(0)
val participantOut1 = OUTPUTS(1)
// Sprawdza, czy członkowie rzeczywiście należą do LETS
val token0 = participant0.tokens(0)._1
val token1 = participant1.tokens(0)._1
val memberTokens = Coll(token0, token1)
val membersExist = membersTree.getMany(memberTokens, lookupProof).forall({ (o: Option[Coll[Byte]]) => o.isDefined })
// Sprawdza, czy zmiany salda członków podczas transakcji są poprawne
val initialBalance0 = participant0.R4[Long].get
val initialBalance1 = participant1.R4[Long].get
val finishBalance0 = participantOut0.R4[Long].get
val finishBalance1 = participantOut1.R4[Long].get
val diff0 = finishBalance0 - initialBalance0
val diff1 = finishBalance1 - initialBalance1
val diffCorrect = diff0 == -diff1
val balancesCorrect = (finishBalance0 > minBalance) && (finishBalance1 > minBalance) && diffCorrect
// Sprawdza, czy boxy członków zapisują swoje skrypty.
// todo: optymalizacja mogłaby być tutaj
val script0Saved = participantOut0.propositionBytes == participant0.propositionBytes
val script1Saved = participantOut1.propositionBytes == participant1.propositionBytes
val scriptsSaved = script0Saved && script1Saved
// Ochrona boxa specyficznego dla członka
val selfPubKey = SELF.R5[SigmaProp].get
selfPubKey && properLetsToken && membersExist && diffCorrect && scriptsSaved
Zauważ, że oba kontrakty mogą być modyfikowane na wiele sposobów, aby uzyskać nowe systemy o różnych właściwościach. Mamy nadzieję, że
pewnego dnia ten artykuł zostanie kontynuowany!
Share post
13 sierpnia 2025
12 maja 2025






