Skip to content

Commit

Permalink
release
Browse files Browse the repository at this point in the history
  • Loading branch information
Lbqds committed Jan 28, 2019
0 parents commit 6b825a4
Show file tree
Hide file tree
Showing 154 changed files with 7,954 additions and 0 deletions.
56 changes: 56 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Created by .ignore support plugin (hsz.mobi)
### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Scala template
*.class
*.log
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

.idea
target

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: scala
scala:
- 2.12.8
script:
- sbt compile
- sbt test
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## eth-abi

[![Build Status](https://travis-ci.com/Lbqds/eth-abi.svg?token=iUBC3d9KBxXjFrs9989Y&branch=master)](https://travis-ci.com/Lbqds/eth-abi)

generate scala code from solidity contract

### codegen

download from the [release](https://github.com/Lbqds/eth-abi/releases) page, then execute:

```
$ tar -xf abi-codegen.tar.gz
$ scala abi-codegen.jar --help
```

it will show usage as follow:

```text
abi-codegen 0.1
Usage: abi-codegen [options]
-a, --abi <abiFile> contract abi file
-b, --bin <binFile> contract bin file
-p, --package <packages> package name e.g. "examples.token"
-c, --className <className> class name
-o, --output <output dir> output directory
-h, --help show usage
```

there are some generated [examples](https://github.com/Lbqds/eth-abi/tree/master/examples/src/main/scala/examples)

### ABIEncoderV2

`eth-abi` support [experimental ABIEncoderV2](https://solidity.readthedocs.io/en/latest/abi-spec.html#handling-tuple-types) feature,
tuple will map to `TupleTypeN`, the generated [exchange](https://github.com/Lbqds/eth-abi/blob/master/examples/src/main/scala/examples/exchange/Exchange.scala)
use this feature heavily.

### Sonatype

`eth-abi` can also be used to interact directly with ethereum, please wait to publish
69 changes: 69 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
val commonSettings = Seq(
organization := "com.github.lbqds",
scalaVersion := "2.12.8",
version := "0.1",
scalacOptions ++= Seq(
"-encoding", "utf8",
// "-Xfatal-warnings",
"-deprecation",
// "-unchecked",
"-language:implicitConversions",
"-language:higherKinds",
"-language:existentials",
// "-Xlog-implicits",
// "-Xlog-implicit-conversions",
"-language:postfixOps"),
test in assembly := {}
)

import xerial.sbt.Sonatype._
val publishSettings = Seq(
publishTo := sonatypePublishTo.value,
sonatypeProfileName := "com.github.lbqds",
publishMavenStyle := true,
licenses := Seq("GPL" -> url("https://www.gnu.org/licenses/gpl-3.0.txt")),
sonatypeProjectHosting := Some(GitHubHosting("lbqds", "eth-abi", "[email protected]")),
homepage := Some(url("https://github.com/lbqds")),
scmInfo := Some(
ScmInfo(
url("https://github.com/lbqds/eth-abi"),
"scm:[email protected]:lbqds/eth-abi.git"
)
),
developers := List(
Developer(id = "lbqds", name = "lbqds", email = "[email protected]", url = new URL("https://github.com/lbqds"))
),
publishConfiguration := publishConfiguration.value.withOverwrite(true),
publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true)
)

lazy val ethabi =
Project(id = "eth-abi", base = file("."))
.settings(commonSettings)
.settings(name := "eth-abi")
.settings(Dependencies.deps)
.settings(publishSettings)
.enablePlugins(spray.boilerplate.BoilerplatePlugin)
.disablePlugins(sbtassembly.AssemblyPlugin)

lazy val codegen =
Project(id = "codegen", base = file("codegen"))
.settings(commonSettings)
.dependsOn(ethabi)
.settings(Dependencies.codegenDeps)
.settings(
name := "codegen",
assemblyJarName := "abi-codegen.jar",
skip in publish := true
)

lazy val example =
Project(id = "examples", base = file("examples"))
.settings(commonSettings)
.settings(Dependencies.examplesDpes)
.dependsOn(ethabi)
.disablePlugins(sbtassembly.AssemblyPlugin)
.settings(
name := "examples",
skip in publish := true
)
183 changes: 183 additions & 0 deletions codegen/src/main/scala/codegen/AbiDefinition.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package codegen

import io.circe.jawn.decode
import io.circe.generic.auto._
import scala.meta._
import ethabi.util.{Hash, Hex}

// fallback function have no name and inputs
case class AbiDefinition(`type`: String, name: Option[String], inputs: Option[Seq[Param]], outputs: Option[Seq[Param]],
stateMutability: Option[String], anonymous: Option[Boolean]) {
import AbiDefinition._
def isPayable: Boolean = stateMutability.isDefined && stateMutability.get == "payable"
def isConstant: Boolean = stateMutability.isDefined && (stateMutability.get == "pure" || stateMutability.get == "view")
def isEvent: Boolean = `type` == "event"
def isFunction: Boolean = `type` == "function"
def isConstructor: Boolean = `type` == "constructor"
def isFallback: Boolean = `type` == "fallback"
def isAnonymous: Boolean = anonymous.isDefined && anonymous.get

private [codegen] val signature =
if (isFunction || isEvent) name.map(_ + "(" + inputs.map(_.map(_.signature).mkString(",")).getOrElse("") + ")")
else None
private [codegen] val signatureHash = signature.map(sig => Hash.hash(sig.getBytes))
private [codegen] val identifier = signatureHash.map(_.bytes.slice(0, 4))
private val args = peel(inputs.map(_.map(p => Term.Param(List.empty, p.termName, Some(p.tpe), None)).toList))
private val argsTpe = peel(inputs.map(_.map(_.tpe).toList))

private val returnType: Option[Type] = {
outputs match {
case None => None
case Some(params) if params.isEmpty => None
case Some(params) => Some(paramsToTuple(params))
}
}

private def funcArgsAndEncodeStat = {
val id = identifier.map(id => Hex.bytes2Hex(id)).getOrElse("")
val defaultEncodeStat = Term.Block(List(q"val encoded = Hex.hex2Bytes(${Lit.String(id)})"))
val encodeStat = for {
params <- args
paramsTpe <- argsTpe
} yield {
Term.Block(List(
q"val paramsEncoded = ${encodeParams(paramsTpe, params, params.map(_.name.asInstanceOf[Term.Name]))}",
q"val functionId = Hex.hex2Bytes(${Lit.String(id)})",
q"val encoded = functionId ++ paramsEncoded",
))
}
(args.getOrElse(List.empty), encodeStat.getOrElse(defaultEncodeStat))
}

private def ctorArgsAndEncodeStat = {
val defaultEncodeStat = Term.Block(List(q"val encoded = Hex.hex2Bytes(binary)"))
val encodeStat = for {
params <- args
paramsTpe <- argsTpe
} yield {
Term.Block(List(
q"val paramsEncoded = ${encodeParams(paramsTpe, params, params.map(_.name.asInstanceOf[Term.Name]))}",
q"val code = Hex.hex2Bytes(binary)",
q"val encoded = code ++ paramsEncoded"
))
}
(args.getOrElse(List.empty), encodeStat.getOrElse(defaultEncodeStat))
}

private [codegen] def genConstructor: Defn = {
assert(isConstructor)
val (args, encodeStat) = ctorArgsAndEncodeStat
val body = Term.Block(encodeStat.stats :+ q"impl.deploy(encoded, sender, opt)")
Defn.Def(List.empty, Term.Name("deploy"), List.empty, List(args :+ sender :+ opt), Some(Type.Name("Unit")), body)
}

private def callAndDecodeStat(retTpe: Option[Type]) = {
if (retTpe.isDefined) {
Term.Block(List(
q"""
impl.call(encoded, sender, opt).map { bytes =>
val result = ${decodeParams(retTpe.get, Term.Name("bytes"))}
result._1
}
"""
))
} else {
Term.Block(List(
q"impl.call(encoded, sender, opt)"
))
}
}

private def genConstantFunction(retTpe: Option[Type]): Defn = {
val (args, encodeStat) = funcArgsAndEncodeStat
val body = Term.Block(encodeStat.stats ++ callAndDecodeStat(retTpe).stats)
Defn.Def(List.empty, Term.Name(name.get), List.empty, List(args :+ sender :+ opt), retTpe.map(t => Type.Apply(Type.Name("Future"), List(t))), body)
}

private def genTransactionFunction(retTpe: Option[Type]): Defn = {
val (args, encodeStat) = funcArgsAndEncodeStat
val body = Term.Block(encodeStat.stats :+ q"impl.sendTransaction(encoded, sender, opt)")
Defn.Def(List.empty, Term.Name(name.get), List.empty, List(args :+ sender :+ opt), retTpe, body)
}

private [codegen] def genFunction: Defn = {
assert(isFunction && name.isDefined)
if (isConstant) genConstantFunction(returnType)
else genTransactionFunction(Some(defaultRetTpe))
}

private [codegen] def genEventDecodeFunc: Defn.Def = {
assert(isEvent && !isAnonymous && name.isDefined)
val typeInfosDecl = q"""var typeInfos = Seq.empty[TypeInfo[SolType]]"""
val indexedTypeInfos = inputs.map(_.filter(_.isIndexed).map(p => q"""typeInfos = typeInfos :+ implicitly[TypeInfo[${p.tpe}]]"""))
val nonIndexTypeInfo = inputs.flatMap { params =>
val tpes = params.filter(!_.isIndexed).map(_.tpe).toList
if (tpes.nonEmpty) {
val tupleType = Type.Name(s"TupleType${tpes.length}")
Some(q"""typeInfos = typeInfos :+ implicitly[TypeInfo[${Type.Apply(tupleType, tpes)}]]""")
} else None
}
var stats: List[Stat] = List(typeInfosDecl)
if (indexedTypeInfos.isDefined) stats = stats ++ indexedTypeInfos.get
if (nonIndexTypeInfo.isDefined) stats = stats :+ nonIndexTypeInfo.get
stats = stats :+ q"""EventValue.decodeEvent(typeInfos, log)"""
Defn.Def(List.empty, Term.Name(s"decode${name.get.capitalize}"), List.empty, List(List(log)), Some(Type.Name("EventValue")), Term.Block(stats))
}

private [codegen] def genSubscribeEventFunc: Defn.Def = {
assert(isEvent && !isAnonymous && name.isDefined)
val funcName = Term.Name(s"subscribe${name.get.capitalize}")
val decodeFunc = Term.Name(s"decode${name.get.capitalize}")
val topic = signatureHash.get.toString
q"""
def $funcName: Source[EventValue, NotUsed] = {
val query = LogQuery.from(contractAddress, Hash(${Lit.String(topic)}))
impl.subscribeLogs(query).map($decodeFunc)
}
"""
}

private [codegen] def genEvent: List[Defn] = List(genEventDecodeFunc, genSubscribeEventFunc)
}

object AbiDefinition {
private val defaultRetTpe = Type.Apply(Type.Name("Future"), List(Type.Name("Hash")))
private val opt = Term.Param(List.empty, Term.Name("opt"), Some(Type.Name("TransactionOpt")), None)
private val sender = Term.Param(List.empty, Term.Name("sender"), Some(Type.Name("Address")), None)
private val log = Term.Param(List.empty, Term.Name("log"), Some(Type.Name("Log")), None)

def apply(json: String): AbiDefinition = decode[AbiDefinition](json).right.get

private def paramsToTuple(params: Seq[Param]): Type = {
if (params.length == 1) params.head.tpe
else {
val paramTypes = params.map(_.tpe)
val tupleType = Type.Name(s"TupleType${paramTypes.length}")
Type.Apply(tupleType, paramTypes.toList)
}
}

private def encodeParams(paramsTpe: List[Type], params: List[Term.Param], inputs: List[Term.Name]): Term.Apply = {
val name = s"TupleType${params.length}"
val typeName = Type.Name(name)
val termName = Term.Name(name)
val applyFunc = Term.Select(termName, Term.Name("apply"))
val bundle = Term.Apply(Term.ApplyType(applyFunc, params.map(_.decltpe.get)), inputs)
val typeInfo = q"implicitly[TypeInfo[${Type.Apply(typeName, paramsTpe)}]]"
val encodeFunc = Term.Select(typeInfo, Term.Name("encode"))
Term.Apply(encodeFunc, List(bundle))
}

private def decodeParams(paramsTpe: Type, input: Term.Name): Term.Apply = {
val typeInfo = q"implicitly[TypeInfo[$paramsTpe]]"
val decodeFunc = Term.Select(typeInfo, Term.Name("decode"))
Term.Apply(decodeFunc, List(input, Lit.Int(0)))
}

private def peel[Coll <: Traversable[_]](coll: Option[Coll]): Option[Coll] = {
coll match {
case Some(c) if c.isEmpty => None
case c => c
}
}
}
Loading

0 comments on commit 6b825a4

Please sign in to comment.