All Downloads are FREE. Search and download functionalities are using the official Maven repository.

liewhite.ethers.abi.Abi.scala Maven / Gradle / Ivy

The newest version!
package liewhite.ethers.abi

import liewhite.json.{*, given}
import zio.schema
import scala.util.Try
import liewhite.ethers.types.Address
import liewhite.json.{*, given}
import zio.Chunk
import liewhite.ethers.*

trait ABIType {
  def toString(): String
  def isDynamic: Boolean = false

  // 头部长度, 大部分都是32
  def headLength: Int = 32

  def encode(params: Any): Array[Byte] // params也应该支持json

  def decode(bs: Array[Byte]): Json // 只返回Json, 方便动态处理, 不然只能到处match

  def encodePacked(params: Any): Array[Byte]
}

// int 对应int256, uint 对应uint256
case class ABITypeUint(size: Int) extends ABIType {

  val MAX: BigInt = BigInt(2).pow(size) - 1
  val MIN: BigInt = 0

  override def toString(): String = s"uint$size"
  def encode(params: Any): Array[Byte] = {
    val bi = params match {
      case i: Int       => BigInt(i)
      case l: Long      => BigInt(l)
      case bi: BigInt   => bi
      case Json.Num(bd) => BigInt(bd.toBigInteger())
    }
    if (bi > MAX || bi < MIN) {
      throw ABIException(s"uint not in range $bi")
    }
    bi.toBytes32
  }
  override def decode(bs: Array[Byte]): Json = {
    val with0 = bs.take(32).prepended(0.toByte)
    Json.Num(BigDecimal(BigInt(with0)))
  }

  override def encodePacked(params: Any): Array[Byte] =
    encode(params).drop((32 - size / 8))
}
case class ABITypeInt(size: Int) extends ABIType {
  val MAX: BigInt = BigInt(2).pow(size - 1) - 1
  val MIN: BigInt = -MAX - 1

  override def toString(): String = s"int$size"
  def encode(params: Any): Array[Byte] =
    val bi = params match {
      case i: Int       => BigInt(i)
      case l: Long      => BigInt(l)
      case bi: BigInt   => bi
      case Json.Num(bd) => BigInt(bd.toBigInteger())
    }
    if (bi > MAX || bi < MIN) {
      throw ABIException(s"int not in range $bi")
    }
    bi.toBytes32

  override def encodePacked(params: Any): Array[Byte] =
    encode(params).drop((32 - size / 8))

  override def decode(bs: Array[Byte]): Json =
    Json.Num(BigDecimal(BigInt(bs.take(32))))
}
case object ABITypeBool extends ABIType {

  override def toString(): String = s"bool"
  def encode(params: Any): Array[Byte] =
    params match {
      case true         => 1.toBytes32
      case false        => 0.toBytes32
      case Json.Bool(v) => encode(v)
    }
  override def encodePacked(params: Any): Array[Byte] =
    Array[Byte](encode(params).last)

  override def decode(bs: Array[Byte]): Json = {
    val value = BigInt(bs.take(32))
    if (value == 0) {
      Json.Bool(false)
    } else if (value == 1) {
      Json.Bool(true)
    } else {
      throw ABIException(s"invalid bool value: ${bs.bytesToHex}")
    }
  }
}

case class ABITypeSizedBytes(size: Int) extends ABIType {

  override def encodePacked(params: Any): Array[Byte] = {
    val bs = params match {
      case bs: Array[Byte] => bs
      case str: String     => str.hexToBytes
      case Json.Str(str)   => str.hexToBytes
    }
    if (bs.length > size) {
      throw ABIException(s"bytes length not in range ${bs.length}")
    }
    bs.alignLength(size)
  }

  override def toString(): String      = s"bytes$size"
  def encode(params: Any): Array[Byte] = encodePacked(params).alignLength()
  override def decode(bs: Array[Byte]): Json =
    Json.Str(bs.take(size).bytesToHex)
}

case object ABITypeBytes extends ABIType {

  override def isDynamic: Boolean = true

  override def toString(): String = s"bytes"
  def encode(params: Any): Array[Byte] =
    val content = encodePacked(params)
    ABITypeUint(256).encode(content.length) ++ content.alignLength()

  override def encodePacked(params: Any): Array[Byte] =
    params match {
      case bs: Array[Byte] => bs
      case str: String     => str.hexToBytes
      case Json.Str(str)   => str.hexToBytes
    }
  override def decode(bs: Array[Byte]): Json = {
    val length = ABITypeUint(256).decode(bs.take(32)).asNumber.get.value.toBigInteger().intValue()
    Json.Str(bs.drop(32).take(length).bytesToHex)
  }
}

case object ABITypeAddress extends ABIType {

  override def encodePacked(params: Any): Array[Byte] = {
    val bs = params match {
      case bs: Array[Byte] => Address.fromBytes(bs)
      case str: String     => Address.fromHex(str)
      case addr: Address   => addr
      case Json.Str(str)   => Address.fromHex(str)
    }
    bs.bs
  }

  override def toString(): String = s"address"

  def encode(params: Any): Array[Byte] =
    encodePacked(params).alignLength(32, "left")

  override def decode(bs: Array[Byte]): Json =
    Json.Str(Address.fromBytes(bs.drop(12).take(20)).checkSumAddress)
}

case object ABITypeString extends ABIType {

  override def isDynamic: Boolean = true
  override def encodePacked(params: Any): Array[Byte] =
    params match {
      case s: String     => s.getBytes()
      case Json.Str(str) => str.getBytes()
    }

  override def toString(): String = s"string"

  def encode(params: Any): Array[Byte] =
    val content = encodePacked(params)
    ABITypeUint(256).encode(content.length) ++ content.alignLength()

  override def decode(bs: Array[Byte]): Json = {
    val length = ABITypeUint(256).decode(bs.take(32)).asNumber.get.value.intValue()
    Json.Str(String(bs.drop(32).take(length)))
  }
}

case class ABITypeSizedArray(elem: ABIType, size: Int) extends ABIType {
  override def headLength: Int = if (!elem.isDynamic) {
    elem.headLength * size
  } else {
    32
  }

  override def isDynamic: Boolean = elem.isDynamic

  override def encodePacked(params: Any): Array[Byte] = {
    val elems = params match {
      case seq: Seq[Any] => seq
      case Json.Arr(vs)  => vs
    }
    elems.map(elem.encodePacked(_)).reduce(_ ++ _)
  }

  override def toString(): String = s"${elem.toString()}[$size]"
  def encode(params: Any): Array[Byte] =
    val elems = params match {
      case seq: Seq[Any] => seq
      case Json.Arr(vs)  => vs
    }
    ABITypeTuple(List.fill(elems.length)(elem)*)
      .encode(Tuple.fromArray(elems.toArray))

  override def decode(bs: Array[Byte]): Json =
    ABITypeTuple(List.fill(size)(elem)*)
      .decode(bs)
}

case class ABITypeArray(elem: ABIType) extends ABIType {

  override def isDynamic: Boolean = true

  override def encodePacked(params: Any): Array[Byte] = {
    val elems = params match {
      case seq: Seq[Any] => seq
      case Json.Arr(vs)  => vs
    }
    elems.map(elem.encodePacked(_)).reduce(_ ++ _)
  }

  override def toString(): String = s"${elem.toString()}[]"
  def encode(params: Any): Array[Byte] = {
    val elems = params match {
      case seq: Seq[Any] => seq
      case Json.Arr(vs)  => vs
    }
    ABITypeUint(256).encode(elems.length) ++ ABITypeTuple(List.fill(elems.length)(elem)*)
      .encode(Tuple.fromArray(elems.toArray))
  }
  override def decode(bs: Array[Byte]): Json = {
    val length = ABITypeUint(256).decode(bs.take(32)).asNumber.get.value.intValue()
    ABITypeTuple(List.fill(length)(elem)*)
      .decode(bs.drop(32))
  }
}

case class ABITypeTuple(elems: ABIType*) extends ABIType {

  override def headLength: Int = if (isDynamic) {
    32
  } else {
    elems.map(_.headLength).sum
  }

  override def isDynamic: Boolean = elems.exists(_.isDynamic)

  override def encodePacked(params: Any): Array[Byte] = {
    val args = params match {
      case tuple: Tuple => tuple.toList.asInstanceOf[List[Any]]
      case seq: Seq[_]  => seq
      case Json.Arr(vs) => vs
    }
    (elems
      .zip(args)
      .map { case (tp, data) =>
        tp.encodePacked(data)
      })
      .reduce(_ ++ _)

  }

  override def toString(): String = s"(${elems.map(_.toString()).mkString(",")})"

  def encode(params: Any): Array[Byte] = {
    val args = params match {
      case tuple: Tuple => tuple.toList.asInstanceOf[List[Any]]
      case seq: Seq[_]  => seq
      case Json.Arr(vs) => vs
    }

    val headsLength = elems.map(_.headLength).sum
    // heads, tails
    val tail = elems.zip(args).foldLeft(Seq.empty[Array[Byte]]) { (accTail, item) =>
      val value = if (!item._1.isDynamic) {
        Array.emptyByteArray
      } else {
        item._1.encode(item._2)
      }
      accTail.appended(value)
    }
    val tailLength = tail.map(_.length)
    val heads = elems.zip(args).zipWithIndex.map {
      case ((tp, arg), index) => {
        if (!tp.isDynamic) {
          tp.encode(arg)
        } else {
          ABITypeUint(256).encode(headsLength + tailLength.take(index).sum)
        }
      }
    }
    heads.reduce(_ ++ _) ++ tail.reduce(_ ++ _)
  }
  override def decode(bs: Array[Byte]): Json = {
    // 先读取heads
    // val headsLength = elems.map(_.headLength).sum
    // val (heads,tails) = bs.splitAt(headsLength)
    // val length = ABITypeUint(256).decode(bs.take(32))
    val result = elems.foldLeft((0, Chunk.empty[Json])) { (acc, item) =>
      val headStartIndex = acc._1
      val headLength     = item.headLength
      val head           = bs.slice(headStartIndex, headStartIndex + headLength)
      val result = if (item.isDynamic) {
        // head就是body的开始位置
        val tailIndex = BigInt(head).intValue
        item.decode(bs.drop(tailIndex))
      } else {
        // 静态类型直接在当前位置decode
        item.decode(head)
      }
      (acc._1 + headLength, acc._2.appended(result))
    }
    Json.Arr(result._2)
  }
}

object ABIType {
  val sugar = Map(
    "int"  -> "int256",
    "uint" -> "uint256"
  )
  def parseType(s: String): ABIType =
    if (s.endsWith("]")) {
      if (s.endsWith("[]")) {
        parseArray(s)
      } else {
        parseSizedArray(s)
      }
    } else if (s.startsWith("(")) {
      parseTuple(s)
    } else {
      // parse atom
      val desugar = sugar.getOrElse(s, s)
      if (desugar == "bytes") {
        ABITypeBytes
      } else if (desugar.startsWith("bytes")) {
        val size = desugar.drop(5).toInt
        ABITypeSizedBytes(size)
      } else if (desugar == "string") {
        ABITypeString
      } else if (desugar == "bool") {
        ABITypeBool
      } else if (desugar == "address") {
        ABITypeAddress
      } else if (desugar.startsWith("int")) {
        parseInt(desugar)
      } else if (desugar.startsWith("uint")) {
        parseUInt(desugar)
      } else {
        throw ABIException(s"unknown type $desugar")
      }
    }

  def parseArray(s: String): ABIType =
    ABITypeArray(parseType(s.dropRight(2)))
  def parseSizedArray(s: String): ABIType = {
    // bytes[] bytes[n] 要单独处理
    val items = s.dropRight(1).split("[")
    val size  = items.last.toInt
    ABITypeSizedArray(parseType(items.head), size)
  }

  def parseTuple(s: String): ABIType = {
    val items = s.drop(1).dropRight(1).split(",").map(parseType(_))
    ABITypeTuple(items*)
  }
  def parseInt(s: String): ABIType = {
    val size = s.drop(3).toInt
    ABITypeInt(size)
  }
  def parseUInt(s: String): ABIType = {
    val size = s.drop(4).toInt
    ABITypeUint(size)
  }

  given Schema[ABIType] = Schema[String].transformOrFail(
    str => {
      Try {
        parseType(str)
      }.toEither.left.map(_.getMessage())
    },
    tp => {
      Right(tp.toString())
    }
  )
}

enum ABIItemType derives Schema {
  case function
  case constructor
  case receive
  case fallback
  case event
  case error
}
case class Input(
  name: String,
  `type`: ABIType,
  components: Option[Seq[Input]],
  indexed: Option[Boolean]
) derives Schema

case class ABIItem(
  `type`: ABIItemType,
  name: Option[String], //Constructor, receive, and fallback never have name or outputs
  inputs: Option[Seq[Input]],
  outputs: Option[Seq[Input]],
  anonymous: Option[Boolean]
) derives Schema

trait ABIValue




© 2015 - 2024 Weber Informatics LLC | Privacy Policy