-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6b825a4
Showing
154 changed files
with
7,954 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
Oops, something went wrong.