Interest-Free Loan Contract

Based on the above description and some small changes, one variant of the loan contract is given at this link

{
      val dataInput = CONTEXT.dataInputs(0)
      val rate = dataInput.R4[Long].get // rate (how many nano-Erg for 1 USD cent)
    
      // Check 1: CORRECT RATE ORACLE (has the correct token id)
      val correctRateOracle = dataInput.tokens(0)._1 == rateOracleTokenID
    
      val out = OUTPUTS(0) // should be same box script
      val currentUSD = SELF.R4[Long].get // how many USD owed to Bob
    
      val lastPaymentHeight = SELF.R5[Int].get // what height last payment was made
      val thisPaymentHeight = out.R5[Int].get // what the current height is
    
      // Check 2: CORRECT HEIGHT (within a day of current HEIGHT and higher than previous height)
      val correctHeight = thisPaymentHeight <= HEIGHT &&
                          thisPaymentHeight >= HEIGHT - 720 && // within a day
                          thisPaymentHeight > lastPaymentHeight
    
      // Check 3: CORRECT SCRIPT
      val correctScript = out.propositionBytes == SELF.propositionBytes
    
      val outUSD = out.R4[Long].get
      val usdDiff = currentUSD - outUSD
      val ergsDiff = SELF.value - out.value
      val correctErgsDiff = usdDiff * rate == ergsDiff
    
      // Check 4: CORRECT Ergs difference
      val correctDiff = usdDiff == emi && correctErgsDiff
    
      val correctTx = correctDiff && correctScript && correctRateOracle && correctHeight
    
      // Four different ways box can be spent
      //
      //   1. Alice makes 10 Euro payment within 35 days of last payment
    
      val bobBox = OUTPUTS(1) // this is the box where Alice will pay to Bob
      val correctBobAmt = bobBox.tokens(0)._1 == usdTokenID && bobBox.tokens(0)._2 == emi
      val correctBobScript = bobBox.propositionBytes == proveDlog(bob).propBytes
      val correctBobBox = correctBobAmt && correctBobScript
      val payment = correctTx && proveDlog(alice) && correctBobBox
    
      //   2. Alice does not make payment within 35 days of last payment. Bob takes out due himself
      val nonPayment = correctTx && proveDlog(bob) && ((HEIGHT - lastPaymentHeight) > (oneMonth + fiveDays))
    
      //   3. Price drops anytime (margin call)
      val marginCall = (currentUSD * rate > SELF.value) && proveDlog(bob)
    
      //   4. Price increases anytime (profit sharing)
      val reqd = currentUSD * rate * 12 / 10
      val profit = (SELF.value - reqd)/2
      val ergPriceHigh = profit > 0
      val profitSharing = ergPriceHigh && correctScript && out.value == reqd && usdDiff == 0 &&
      OUTPUTS(1).propositionBytes == proveDlog(bob).propBytes && OUTPUTS(1).value == profit &&
      OUTPUTS(2).propositionBytes == proveDlog(alice).propBytes && OUTPUTS(2).value == profit &&
      lastPaymentHeight == thisPaymentHeight
    
      anyOf(
        Coll(
          profitSharing,
          marginCall,
          payment,
          nonPayment
        )
      )
    
    }

This is based on the One-Way USD Token idea described in this post, and whose code is given here.

To quote from that post:

The one-way convertible token is defined as follows:

Bob can create a “token box” with a large number of “USD tokens” that can be exchanged for Ergs at the current rate in USD/Erg. The contract in the token box only allows changing Ergs to tokens and not the other way round.

So Bob could create a box with 1 trillion tokens such that anyone can pay ergs and purchase tokens at the inverse rate of USD/ergs. If the rate is 10 USD/Erg, then anyone can purchase X number of tokens by paying X/10 Ergs.

One-way USD token (1USD) allows Alice to pay the loan back without Bob’s intervention and takes care of the cheating Bob scenario discussed above, where Bob refuses to acknowledge Alice’s repayment.

The other way Bob could attack the system is to make the 1USD tokens unavailable for purchase. This can be handled by keeping a large number of tokens that can never be exhausted using the available ERGs. The code of 1USD token is reproduced below

{
    val newSelf = OUTPUTS(0) // new box created as a replica of current box
    val bobOut = OUTPUTS(1) // box paying to Bob
  
    val bobNanoErgs = bobOut.value
    val validBobBox = bobOut.propositionBytes == proveDlog(bob).propBytes
  
    val selfTokenID = SELF.tokens(0)._1
    val selfTokenAmt = SELF.tokens(0)._2
  
    val newSelfTokenID = newSelf.tokens(0)._1
    val newSelfTokenAmt = newSelf.tokens(0)._2
    val validTokenID = selfTokenID == newSelfTokenID
    val validProp = newSelf.propositionBytes == SELF.propositionBytes
  
    val tokenDiff = selfTokenAmt - newSelfTokenAmt
    val validNewSelf = validTokenID && validProp
  
    val rateBox = CONTEXT.dataInputs(0)
    val rate = rateBox.R4[Long].get
    val validRateBox = rateBox.tokens(0)._1 == rateTokenID
  
    // rate gives nanoErg per USDCent 
    // Thus, bobNanoErgs NanoErgs will cost bobNanoErgs / rate usd cents
  
    val usdCDiff = bobNanoErgs / rate
  
    tokenDiff <= usdCDiff && validRateBox && validNewSelf && validBobBox
}
5 Likes