本地交易所交易系统(LETS)是一个本地相互信用协会,允许成员单独创建共同信用资金,系统中的所有交易都写入共同的分类账。例如,假设拥有零余额的Alice愿意从Bob购买一升生鲜奶。首先,他们就价格达成一致,例如,假设价格约为2欧元(Alice和Bob居住在爱尔兰)。交易被写入分类账后,Alice的余额变为-2(减去2)欧元,Bob的余额变为2欧元。然后Bob可能会花费2欧元来购买Charlie的自制啤酒。通常,这种制度对负余额施加限制,有时甚至对正余额施加限制,以促进社区交易。
从历史上看,这种系统在危机时期变得流行起来。第一个系统是迈克尔林顿于1981年在加拿大陷入萧条时的城镇建立的。1998-2002年阿根廷大萧条期间,当地的交易所交易系统非常受欢迎。大多数LETS团体的成员从50到250人不等,由核心委员会维护纸质信用票据和分类账。然而,基于纸张的LETS货币已经出现了一些问题,例如伪造票据,系统管理员可能的流氓行为等等。因此,基于区块链的LETS可能优于旧系统。有关LETS的更多信息可以在“货币生态”(The Ecology of Money”)一书(作者RichardDouthwaite) 和维基百科中找到。
在本文中,我们将展示如何在尔格之上实现LETS。据我们所知,这是区块链上首次执行这种社区货币。我们的参考执行很简单,包括两个合同,即管理合同和交易合同。我们跳过尔格初步行动,所以请起步者阅读 首次币发行文章和ErgoScript教程(基础和 高级)。不过,我们将在以下句子中介绍几个新术语。如果发出的代币数量等于1,我们将其称为单例代币。类似地,包含单例代币的币箱称为单例币箱。
管理合约控制一个单例币箱,该币箱容纳LETS系统的会员。合约允许以每笔交易一个会员的速度添加新会员。该币箱不存储会员,而是仅构建在会员目录之上的经过验证的数据结构的小摘要。会员与在交易中发出的单例代币相关联,该交易将会员添加到目录中。该交易创建一个新会员的币箱,其中包含会员的单例代币。会员的盒子受交换合同的保护。此外,新创建会员币箱的初始余额写入R4寄存器,在我们的示例中余额等于零。创建新会员的交易必须提供目录转换的正确性证明。
管理合约币箱通常由委员会控制,委员会随着时间的推移而发展。为了支持这一点,我们允许委员会逻辑驻留在寄存器R5中。例如,假设已添加新的委员会会员以及新的LETS会员,输入管理合约币箱需要2/3个签名,输出币箱需要3/4个签名。在这种情况下,输入和输出币箱中R5寄存器的内容会有所不同。
下面提供了尔格脚本中带有注释的管理合约代码。请注意“userContractHash”(用户合约哈希)是关于交易合约哈希。
val selfOut = OUTPUTS(0)
// 管理脚本
val managementScript = selfOut.R5[SigmaProp].get
// 管理脚本模板正在自我复制,并且管理脚本已被满足
val scriptCorrect = (selfOut.propositionBytes == SELF.propositionBytes) && managementScript
// 支出交易正在为目录,用户,费用创建币箱。
val outsSizeCorrect = OUTPUTS.size == 3
// 检查管理标签代币是否正在自我复制
val outTokenCorrect = (selfOut.tokens.size == 1) && (selfOut.tokens(0)._1 == letsToken)
// 检查新代币是否发行,其数量是否正确
// OUTPUTS(0) 已经通过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)
// 检查是否已使用零余额创建新用户
val zeroUserBalance = userOut.R4[Long].get == 0
val properUserScript = blake2b256(userOut.propositionBytes) == userContractHash
// 检查新代币标识符是否已添加到目录中
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
交易合约脚本非常简单,下面提供了描述其逻辑的注释。 在合约中,假设交易合约币箱的支出交易正在接收至少两个输入,并且前两个输入应受交易合约脚本保护并包含LETS成员代币。 要检查输入中的单例会员标记是否确实属于LETS系统,开销交易会将管理合约币箱作为第一个只读数据输入,并且还应提供会员标记确实属于经过验证的目录的证据。 通过管理合约币箱的R4注册。 脚本中的“letsToken”是关于管理币箱的单例代币。
// LETS交易者允许的最小余额
val minBalance = -20000
val lookupProof = getVar[Coll[Byte]](1).get
// 包含LETS会员目录的只读币箱
val treeHolderBox = CONTEXT.dataInputs(0)
val properLetsToken = treeHolderBox.tokens(0)._1 == letsToken
val membersTree = treeHolderBox.R4[AvlTree].get
// 支出交易需要愿意交易的LETS会员的两个币箱,
// 并返回修改余额的币箱。
val participant0 = INPUTS(0)
val participant1 = INPUTS(1)
val participantOut0 = OUTPUTS(0)
val participantOut1 = OUTPUTS(1)
//检查成员是否确实属于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 })
// 检查交易期间LETS会员余额的变化是否正确
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
// 检查会员币箱是否保存了脚本。
// todo:可以在这里进行优化
val script0Saved = participantOut0.propositionBytes == participant0.propositionBytes
val script1Saved = participantOut1.propositionBytes == participant1.propositionBytes
val scriptsSaved = script0Saved && script1Saved
// 会员特定的币箱保护
val selfPubKey = SELF.R5[SigmaProp].get
selfPubKey && properLetsToken && membersExist && diffCorrect && scriptsSaved
请注意,可以通过多种方式修改这两个合约,以获得具有不同属性的新系统。 所以希望有一天这篇文章会有后续!