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

ethabi.protocol.Contract.scala Maven / Gradle / Ivy

package ethabi.protocol

import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.{Failure, Success}
import akka.actor.ActorSystem
import akka.stream.Materializer
import ethabi.types.{Address, SolType, TupleType, TypeInfo}
import ethabi.util.{Hash, Hex}
import ethabi.protocol.ws.Client
import ethabi.protocol.Request._
import ethabi.protocol.Response.Log
import ethabi.types.generated.Bytes32

final class Contract(val endpoint: String) {
  private implicit val system = ActorSystem()
  private implicit val materializer = Materializer(system)
  private var contractCreator: Option[Address] = None
  private var contractAddress: Option[Address] = None
  private val client = Client(endpoint)

  implicit def dispatcher = system.dispatcher
  def address: Option[Address] = contractAddress
  def creator: Option[Address] = contractCreator
  def load(address: Address): Unit = contractAddress = Some(address)
  def isDeployed: Boolean = contractAddress.isDefined

  def sendTransaction(data: Array[Byte], sender: Address, opt: TransactionOpt): Future[Hash] = {
    if (contractAddress.isEmpty) throw new RuntimeException("contract address is empty when call contract method")
    val transaction = Transaction(sender, contractAddress, data, opt)
    client.sendTransaction(transaction).map {
      case Left(responseError) => throw new RuntimeException(s"send transaction failed, $responseError")
      case Right(None) => throw new RuntimeException(s"no transaction hash after send succeed")
      case Right(Some(txHash)) => txHash
    }
  }

  def call(data: Array[Byte], sender: Address, opt: TransactionOpt): Future[Array[Byte]] = {
    if (contractAddress.isEmpty) throw new RuntimeException("contract address is empty when call contract method")
    val callData = Transaction(sender, contractAddress, data, opt)
    client.call(callData).map {
      case Left(responseError) => throw new RuntimeException(s"call contract failed, $responseError")
      case Right(None) => Array.empty[Byte]
      case Right(Some(value)) => value
    }
  }

  // data include all constructor arguments
  def deploy(data: Array[Byte], sender: Address, opt: TransactionOpt): Unit = {
    contractCreator = Some(sender)
    val transaction = Transaction(sender, None, data, opt)
    client.sendTransaction(transaction) onComplete {
      case Success(response) => response match {
        case Right(Some(txHash)) => afterDeploy(txHash)
        case Left(responseError) => throw new RuntimeException(s"deploy contract failed: $responseError")
        case Right(None) => throw new RuntimeException("deploy contract failed, no tx hash return")
      }
      case Failure(exception) => throw new RuntimeException(s"deploy contract failed: $exception")
    }
  }

  private def afterDeploy(txHash: Hash): Unit =
    client.transactionReceipt(txHash) onComplete {
      case Success(response) => response match {
        case Left(responseError) => throw new RuntimeException(s"deploy contract failed: $responseError")
        case Right(None) => system.scheduler.scheduleOnce(2 seconds)(afterDeploy(txHash))
        case Right(Some(receipt)) =>
          assert(receipt.contractAddress.isDefined)
          // call `get` explicitly
          contractAddress = Some(Address(receipt.contractAddress.get))
      }
      case Failure(exception) => throw exception
    }

  def subscribeLogs(logQuery: LogQuery) = client.subscribeLogs(logQuery)
}

object Contract {
  def apply(endpoint: String) = new Contract(endpoint)
}

final case class EventValue(indexedValues: Seq[SolType], nonIndexedValues: Seq[SolType]) {
  override def toString: String = {
    s"""
       |{
       |  indexedValues: ${indexedValues.mkString("[", ", ", "]")},
       |  nonIndexedValues: ${nonIndexedValues.mkString("[", ", ", "]")}
       |}
    """.stripMargin
  }
}

object EventValue {
  def decodeEvent(typeInfos: Seq[TypeInfo[_ <: SolType]], log: Log): EventValue = {
    val topics = log.topics.slice(1, log.topics.length).map(Hex.hex2Bytes)
    val data = Hex.hex2Bytes(log.data)
    val indexedValues = topics.zip(typeInfos).map {
      case (bytes, typeInfo) =>
        if (typeInfo.isStatic) typeInfo.decode(bytes, 0)._1
        else Bytes32(bytes)
    }
    val nonIndexedTypeInfo = typeInfos.slice(topics.length, typeInfos.length).headOption
    val nonIndexedValues = nonIndexedTypeInfo.map(_.decode(data, 0)._1.asInstanceOf[TupleType].toSeq)
    EventValue(indexedValues, nonIndexedValues.getOrElse(Seq.empty))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy