Skip to content

Commit

Permalink
Closes #30: Added tuple support (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
rmeissner authored and fmrsabino committed Sep 20, 2017
1 parent 01426c3 commit fb985a0
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 138 deletions.
1 change: 1 addition & 0 deletions bivrost-abi-parser/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ task runSolidityTypeGenerator(type: JavaExec) {

dependencies {

implementation project(':bivrost-utils')
implementation project(':bivrost-solidity-types')

implementation deps.kotlin.stdLibJre8
Expand Down
264 changes: 190 additions & 74 deletions bivrost-abi-parser/src/main/kotlin/pm/gnosis/AbiParser.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class AbiElementJson(@Json(name = "constant") val constant: Boolean,
@Json(name = "type") val type: String)

class ParameterJson(@Json(name = "name") val name: String,
@Json(name = "type") val type: String)
@Json(name = "type") val type: String,
@Json(name = "components") val components: List<ParameterJson>?)
88 changes: 77 additions & 11 deletions bivrost-abi-parser/src/test/kotlin/pm/gnosis/AbiParserTest.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package pm.gnosis


import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.asTypeName
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import pm.gnosis.model.AbiRoot
import pm.gnosis.model.ParameterJson
import pm.gnosis.model.Solidity
import pm.gnosis.model.SolidityBase
import java.math.BigInteger
Expand All @@ -15,58 +18,108 @@ import kotlin.reflect.KClass

class AbiParserTest {

private fun testContext() = AbiParser.GeneratorContext(AbiRoot(ArrayList(), "Test"))

private fun testParameter(type: String, name: String = "test", components: List<ParameterJson>? = null)
= ParameterJson(name, type, components)

private fun assertType(instance: Any, type: KClass<*>) {
assertTrue("$instance should be a $type", type.isInstance(instance))
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidArrayDefOpeningBracketStart() {
AbiParser.mapType("uint[[5][]")
AbiParser.mapType(testParameter("uint[[5][]"), testContext())
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidArrayDefOpeningBracketMiddle() {
AbiParser.mapType("uint[5][[]")
AbiParser.mapType(testParameter("uint[5][[]"), testContext())
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidArrayDefOpeningBracketEnd() {
AbiParser.mapType("uint[5][][")
AbiParser.mapType(testParameter("uint[5][]["), testContext())
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidArrayDefLetterAsSize() {
AbiParser.mapType("uint[a][]")
AbiParser.mapType(testParameter("uint[a][]"), testContext())
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidArrayDefClosingBracket() {
AbiParser.mapType("uint[5][]]")
AbiParser.mapType(testParameter("uint[5][]]"), testContext())
}

@Test(expected = IllegalArgumentException::class)
fun testInvalidArrayDefUnknownType() {
AbiParser.mapType("gnosis[1][]")
AbiParser.mapType(testParameter("gnosis[1][]"), testContext())
}

@Test()
fun testParseAliasTypes() {
val uintType = AbiParser.mapType("uint")
val uintType = AbiParser.mapType(testParameter("uint"), testContext())
assertType(uintType, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.UInt256::class.asClassName(), uintType.toTypeName())

val intType = AbiParser.mapType("int")
val intType = AbiParser.mapType(testParameter("int"), testContext())
assertType(intType, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.Int256::class.asClassName(), intType.toTypeName())

val byteType = AbiParser.mapType("byte")
val byteType = AbiParser.mapType(testParameter("byte"), testContext())
assertType(byteType, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.Bytes1::class.asClassName(), byteType.toTypeName())
}

@Test()
fun testParseDynamicTupleType() {
val components = listOf(testParameter("uint", "a"), testParameter("uint[]", "b"))
val tupleType = AbiParser.mapType(testParameter("tuple", components = components), testContext())
assertType(tupleType, AbiParser.TupleTypeHolder::class)
val pTuple = tupleType as AbiParser.TupleTypeHolder
assertEquals(2, pTuple.entries.size)
assertEquals("a", pTuple.entries[0].first)
assertType(pTuple.entries[0].second, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.UInt256::class.asClassName(), pTuple.entries[0].second.toTypeName())

assertEquals("b", pTuple.entries[1].first)
assertType(pTuple.entries[1].second, AbiParser.VectorTypeHolder::class)
val pArray = pTuple.entries[1].second as AbiParser.VectorTypeHolder
assertType(pArray.itemType, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.UInt256::class.asClassName(), pArray.itemType.toTypeName())

assertEquals(true, tupleType.isDynamic())
assertEquals("TupleA", tupleType.name)
assertEquals(ClassName("", "TupleA"), tupleType.toTypeName())
}

@Test()
fun testStaticDynamicTupleType() {
val components = listOf(testParameter("uint", "a"), testParameter("uint[5]", "b"))
val tupleType = AbiParser.mapType(testParameter("tuple", components = components), testContext())
assertType(tupleType, AbiParser.TupleTypeHolder::class)
val pTuple = tupleType as AbiParser.TupleTypeHolder
assertEquals(2, pTuple.entries.size)
assertEquals("a", pTuple.entries[0].first)
assertType(pTuple.entries[0].second, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.UInt256::class.asClassName(), pTuple.entries[0].second.toTypeName())

assertEquals("b", pTuple.entries[1].first)
assertType(pTuple.entries[1].second, AbiParser.ArrayTypeHolder::class)
val pArray = pTuple.entries[1].second as AbiParser.ArrayTypeHolder
assertType(pArray.itemType, AbiParser.SimpleTypeHolder::class)
assertEquals(Solidity.UInt256::class.asClassName(), pArray.itemType.toTypeName())
assertEquals(5, pArray.capacity)

assertEquals(false, tupleType.isDynamic())
assertEquals("TupleA", tupleType.name)
assertEquals(ClassName("", "TupleA"), tupleType.toTypeName())
}

@Test()
fun testParseUIntNestedArray() {
val type = AbiParser.mapType("uint[5][]")
val type = AbiParser.mapType(testParameter("uint[5][]"), testContext())
assertType(type, AbiParser.VectorTypeHolder::class)
val pType = type as AbiParser.VectorTypeHolder
assertEquals(SolidityBase.VectorST::class.asClassName(), pType.listType)
Expand All @@ -84,7 +137,7 @@ class AbiParserTest {

@Test()
fun testParseStringDynamicArray() {
val type = AbiParser.mapType("string[]")
val type = AbiParser.mapType(testParameter("string[]"), testContext())
assertType(type, AbiParser.VectorTypeHolder::class)
val pType = type as AbiParser.VectorTypeHolder
assertEquals(SolidityBase.VectorDT::class.asClassName(), pType.listType)
Expand All @@ -94,6 +147,19 @@ class AbiParserTest {
assertEquals(Solidity.String::class.asTypeName(), g1Type.toTypeName())
}

@Test()
fun testParseStringStaticArray() {
val type = AbiParser.mapType(testParameter("string[5]"), testContext())
assertType(type, AbiParser.ArrayTypeHolder::class)
val pType = type as AbiParser.ArrayTypeHolder
assertEquals(5, pType.capacity)
assertEquals(SolidityBase.ArrayDT::class.asClassName(), pType.listType)

// First generic type
val g1Type = pType.itemType
assertEquals(Solidity.String::class.asTypeName(), g1Type.toTypeName())
}

@Test
fun testDecodeFunctionArguments() {
/*
Expand Down
1 change: 1 addition & 0 deletions bivrost-solidity-types-generator/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ task runSolidityTypeGenerator(type: JavaExec) {
dependencies {
implementation deps.kotlin.stdLibJre8

implementation project(':bivrost-utils')
implementation project(':bivrost-solidity-types')
implementation 'com.squareup:kotlinpoet:0.4.0'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,48 +74,14 @@ fun generate(path: String, packageName: String) {
kotlinFile.indent(indentation).addType(solidityGeneratedObject.build()).build().writeTo(File(path.removeSuffix(modelPackageName)))
}

private fun generateDecoder(name: String, decodeCode: CodeBlock, isDynamic: Boolean = true): TypeSpec {
val className = ClassName("", name)
return TypeSpec.classBuilder("Decoder")
.addSuperinterface(ParameterizedTypeName.get(SolidityBase.TypeDecoder::class.asClassName(), className))
.addFun(generateIsDynamicFunction(isDynamic))
.addFun(generateDecodeSourceFunction(decodeCode, className))
.build()
}

private fun generateDecodeSourceFunction(decodeCode: CodeBlock, className: ClassName): FunSpec {
return FunSpec.builder("decode")
.addParameter("source", SolidityBase.PartitionData::class)
.addModifiers(KModifier.OVERRIDE)
.returns(className)
.addCode(decodeCode)
.build()
}

private fun generateIsDynamicFunction(isDynamic: Boolean): FunSpec {
return FunSpec.builder("isDynamic")
.addModifiers(KModifier.OVERRIDE)
.returns(Boolean::class)
.addCode(CodeBlock.of("return %L", isDynamic))
.build()
}

private fun generateDecoderCompanion(decoderTypeName: TypeName, decoderInit: CodeBlock): TypeSpec {
return TypeSpec.companionObjectBuilder()
.addProperty(PropertySpec.builder("DECODER", decoderTypeName)
.initializer(decoderInit)
.build())
.build()
}

private fun generateUInts(): List<TypeSpec> = (8..256 step 8).map { generateUInt("UInt$it", it) }.toList()

private fun generateUInt(className: String, nBits: Int): TypeSpec {
val decoderTypeName = ParameterizedTypeName.get(SolidityBase.UIntBase.Decoder::class.asClassName(), ClassName("", className))
return TypeSpec.classBuilder(className)
.superclass(SolidityBase.UIntBase::class)
.addSuperclassConstructorParameter("%1L, %2L", "value", nBits)
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T({ %2L(it) })", decoderTypeName, className)))
.primaryConstructor(FunSpec.constructorBuilder().addParameter(
Expand All @@ -132,7 +98,7 @@ private fun generateAddress(): TypeSpec {
return TypeSpec.classBuilder(name)
.superclass(SolidityBase.UIntBase::class)
.addSuperclassConstructorParameter("%1L, %2L", "value", "160")
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T({ %2L(it) })", decoderTypeName, name)))
.primaryConstructor(FunSpec.constructorBuilder().addParameter(
Expand All @@ -154,13 +120,13 @@ private fun generateBool(): TypeSpec {
.addProperty(PropertySpec.builder("value", Boolean::class)
.initializer("value")
.build())
.addType(generateDecoder(name,
.addType(GeneratorUtils.generateDecoder(name,
CodeBlock.builder()
.addStatement("return %1N(%2T.decodeBool(%3L.consume()))", name, SolidityBase::class, "source")
.build(),
false)
)
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T()", decoderTypeName)))
.build()
Expand All @@ -173,7 +139,7 @@ private fun generateInt(className: String, nBits: Int): TypeSpec {
return TypeSpec.classBuilder(className)
.superclass(SolidityBase.IntBase::class)
.addSuperclassConstructorParameter("%1L, %2L", "value", nBits)
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T({ %2L(it) })", decoderTypeName, className)))
.primaryConstructor(FunSpec.constructorBuilder().addParameter(
Expand All @@ -190,7 +156,7 @@ private fun generateStaticBytes() = (1..32).map {
TypeSpec.classBuilder(name)
.superclass(SolidityBase.StaticBytes::class)
.addSuperclassConstructorParameter("%1L, %2L", "bytes", it)
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T({ %2L(it) }, %3L)", decoderTypeName, name, it)))
.primaryConstructor(FunSpec.constructorBuilder().addParameter(
Expand Down Expand Up @@ -230,11 +196,11 @@ private fun generateDynamicBytes(): TypeSpec {
.addStatement("return %1T(length, contents)", SolidityBase.DynamicType.Parts::class)
.build())
.build())
.addType(generateDecoder(name,
.addType(GeneratorUtils.generateDecoder(name,
CodeBlock.builder()
.addStatement("return %1N(%2T.decodeBytes(source))", name, SolidityBase::class)
.build()))
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T()", decoderTypeName)))
.build()
Expand All @@ -252,12 +218,12 @@ private fun generateString(): TypeSpec {
.addProperty(PropertySpec.builder("value", String::class)
.initializer("value")
.build())
.addType(generateDecoder(name,
.addType(GeneratorUtils.generateDecoder(name,
CodeBlock.builder()
.addStatement("return %1N(%2T.decodeString(%3L))", name, SolidityBase::class, "source")
.build())
)
.companionObject(generateDecoderCompanion(
.companionObject(GeneratorUtils.generateDecoderCompanion(
decoderTypeName,
CodeBlock.of("%1T()", decoderTypeName)))
.build()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package pm.gnosis.model

import java.lang.Exception
import java.math.BigInteger
import kotlin.Boolean
import kotlin.ByteArray
import kotlin.collections.Map
import pm.gnosis.utils.padEndMultiple
import pm.gnosis.utils.toHex
import java.lang.Exception
import java.math.BigInteger

/**
* Generated code. Do not modify
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private val hexArray = "0123456789abcdef".toCharArray()
fun ByteArray.toHex(): String {
val hexChars = CharArray(this.size * 2)
for (j in this.indices) {
val v = (this[j] and 0xFF.toByte()).toInt()
val v = ((this[j] and 0xFF.toByte()).toInt() + 256) % 256
hexChars[j * 2] = hexArray[v ushr 4]
hexChars[j * 2 + 1] = hexArray[v and 0x0F]
}
Expand Down
46 changes: 46 additions & 0 deletions bivrost-utils/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'maven'

sourceCompatibility = 1.8

task runSolidityTypeGenerator(type: JavaExec) {
def srcDirs = sourceSets.main.kotlin.getSrcDirs()
if (srcDirs.isEmpty()) {
logger.error("Couldn't find kotlin main source sets")
return
}

def path = "${sourceSets.main.kotlin.getSrcDirs().getAt(0)}"
main = 'pm.gnosis.SolidityTypeGenerator'
classpath = sourceSets.main.runtimeClasspath
args (path, project.group)
}

dependencies {

implementation project(':bivrost-solidity-types')

implementation deps.kotlin.stdLibJre8

implementation 'com.squareup.moshi:moshi:1.4.0'
implementation 'com.squareup:kotlinpoet:0.4.0'
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.57'

testImplementation group: 'junit', name: 'junit', version: '4.12'
}

compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}

uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('../repo'))
}
}
}
Loading

0 comments on commit fb985a0

Please sign in to comment.