尔格之上的本地交易所交易系统

author img

Alex Chepurnoy

2019.04.22

blog photo

本地交易所交易系统(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

请注意,可以通过多种方式修改这两个合约,以获得具有不同属性的新系统。 所以希望有一天这篇文章会有后续!

分享转发

相关阅读