Browse Source

server: Add the TPoSContract model

master
Alexis Hernandez 6 years ago
parent
commit
c6800a1fc7
  1. 68
      server/app/com/xsn/explorer/models/TPoSContract.scala
  2. 50
      server/test/com/xsn/explorer/models/TPoSContractSpec.scala

68
server/app/com/xsn/explorer/models/TPoSContract.scala

@ -0,0 +1,68 @@
package com.xsn.explorer.models
import com.xsn.explorer.models.values.{Address, TransactionId}
import enumeratum._
import scala.util.Try
case class TPoSContract(
id: TPoSContract.Id,
details: TPoSContract.Details,
time: Long,
state: TPoSContract.State) {
val txid: TransactionId = id.txid
}
object TPoSContract {
case class Id(txid: TransactionId, index: Int)
class Commission private (val int: Int) extends AnyVal
object Commission {
val range = 1 until 100
def from(int: Int): Option[Commission] = {
if (range contains int) Some(new Commission(int))
else None
}
}
case class Details(owner: Address, merchant: Address, merchantCommission: Commission)
object Details {
/**
* Try to get the contract details from the output script ASM.
*
* expected format:
* - "OP_RETURN [hex_encoded_owner_address] [hex_encoded_merchant_address] [owner_commission] [signature]
*
* example:
* - "OP_RETURN 5869337351664d51737932437a4d5a54726e4b573648464770315671465468644c77 58794a4338786e664672484e634d696e68366778755052595939484361593944416f 99"
*/
def fromOutputScriptASM(asm: String): Option[Details] = {
val parts = asm.split(" ").toList
parts match {
case op :: owner :: merchant :: commission :: signature :: Nil if op == "OP_RETURN" =>
for {
ownerAddress <- Address.fromHex(owner)
merchantAddress <- Address.fromHex(merchant)
ownerCommission <- Try(commission.toInt).toOption
merchantCommission <- TPoSContract.Commission.from(100 - ownerCommission)
} yield Details(owner = ownerAddress, merchant = merchantAddress, merchantCommission = merchantCommission)
case _ => None
}
}
}
sealed abstract class State(override val entryName: String) extends EnumEntry
object State extends Enum[State] {
val values = findValues
final case object Active extends State("ACTIVE")
final case object Closed extends State("CLOSED")
}
}

50
server/test/com/xsn/explorer/models/TPoSContractSpec.scala

@ -0,0 +1,50 @@
package com.xsn.explorer.models
import com.xsn.explorer.models.values.Address
import javax.xml.bind.DatatypeConverter
import org.scalatest.MustMatchers._
import org.scalatest.OptionValues._
import org.scalatest.WordSpec
class TPoSContractSpec extends WordSpec {
val address1 = Address.from("Xi3sQfMQsy2CzMZTrnKW6HFGp1VqFThdLw").get
val address2 = Address.from("XyJC8xnfFrHNcMinh6gxuPRYY9HCaY9DAo").get
val address1Hex = DatatypeConverter.printHexBinary(address1.string.getBytes())
val address2Hex = DatatypeConverter.printHexBinary(address2.string.getBytes())
val commission = "99"
val signature = "1f60a6a385a4e5163ffef65dd873f17452bb0d9f89da701ffcc5a0f72287273c0571485c29123fef880d2d8169cfdb884bf95a18a0b36461517acda390ce4cf441"
val failureCases = Map(
"fail when the signature is missing" -> s"OP_RETURN $address1Hex $address2Hex $commission",
"fail when there is an extra field" -> s"OP_RETURN $address1Hex $address2Hex $commission $signature $signature",
"fail if OP_RETURN is not present" -> s"OP_RTURN $address1Hex $address2Hex $commission $signature",
"fail if the commission is missing" -> s"OP_RETURN $address1Hex $address2Hex $signature",
"fail if the commission is corrupted" -> s"OP_RETURN $address1Hex $address2Hex $commission$commission $signature",
"fail if the commission is 0" -> s"OP_RETURN $address1Hex $address2Hex 0 $signature",
"fail if the commission is 100" -> s"OP_RETURN $address1Hex $address2Hex 100 $signature",
"fail if the owner address is malformed" -> s"OP_RETURN x$address1Hex $address2Hex $commission $signature",
"fail if the merchant address is malformed" -> s"OP_RETURN x$address1Hex $address2Hex $commission $signature"
)
"parsing details" should {
"succeed on a valid contract" in {
val asm = s"OP_RETURN $address1Hex $address2Hex $commission $signature"
val expected = TPoSContract.Details(
owner = address1,
merchant = address2,
merchantCommission = TPoSContract.Commission.from(100 - commission.toInt).get)
val result = TPoSContract.Details.fromOutputScriptASM(asm)
result.value must be(expected)
}
failureCases.foreach { case (test, input) =>
test in {
val result = TPoSContract.Details.fromOutputScriptASM(input)
result must be(empty)
}
}
}
}
Loading…
Cancel
Save