Skip to content

Commit

Permalink
Closes #49 improve decoding (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
rmeissner committed Jan 9, 2019
1 parent 1c0efeb commit 5de310b
Show file tree
Hide file tree
Showing 35 changed files with 490 additions and 233 deletions.
71 changes: 35 additions & 36 deletions bivrost-abi-parser/src/main/kotlin/pm/gnosis/AbiParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import com.squareup.moshi.Moshi
import pm.gnosis.model.*
import pm.gnosis.utils.generateSolidityMethodId
import java.io.File
import java.math.BigInteger

object AbiParser {
internal const val DECODER_FUN_ARG_NAME = "data"
internal const val DECODER_VAR_PARTITIONS_NAME = "source"
internal const val DECODER_VAR_ARG_PREFIX = "arg" //arg0, arg1...
internal const val DECODER_VAR_ARG_OFFSET_SUFFIX = "Offset"
internal const val INDENTATION = " "
internal lateinit var context: GeneratorContext

Expand All @@ -33,9 +35,9 @@ object AbiParser {

class ArraysMap(basePackageName: String, private val map: MutableMap<Int, ClassName> = HashMap()) {

private val arraysPackageName = basePackageName + ".arrays"
private val arraysPackageName = "$basePackageName.arrays"

fun get(capacity: Int) = map.getOrPut(capacity, { ClassName(arraysPackageName, "Array" + capacity) })
fun get(capacity: Int) = map.getOrPut(capacity) { ClassName(arraysPackageName, "Array" + capacity) }

fun generate(output: File) {
map.forEach {
Expand All @@ -44,7 +46,7 @@ object AbiParser {

val kotlinFile = FileSpec.builder(arraysPackageName, arrayType.simpleName())

val typeVariable = TypeVariableName.Companion.invoke("T", SolidityBase.Type::class)
val typeVariable = TypeVariableName.invoke("T", SolidityBase.Type::class)
val itemsType = ParameterizedTypeName.get(List::class.asClassName(), typeVariable)

val parameterizedClassType = ParameterizedTypeName.get(arrayType, typeVariable)
Expand Down Expand Up @@ -138,9 +140,7 @@ object AbiParser {

//Generate decodings
val decodeBlockBuilder = CodeBlock.builder()
val locationArgs = ArrayList<Pair<String, TypeHolder>>()
generateStaticArgDecoding(typeHolder, decodeBlockBuilder, locationArgs)
generateDynamicArgDecoding(locationArgs, decodeBlockBuilder)
generateStaticArgDecoding(typeHolder, decodeBlockBuilder)
decodeBlockBuilder.addStatement("return %1N(${(0 until typeHolder.entries.size).joinToString(", ") { "arg$it" }})", typeHolder.name)

builder
Expand All @@ -155,9 +155,9 @@ object AbiParser {
}.toList()


private fun generateStaticArgDecoding(typeHolder: TupleTypeHolder, function: CodeBlock.Builder, locationArgs: MutableCollection<Pair<String, TypeHolder>>) {
private fun generateStaticArgDecoding(typeHolder: TupleTypeHolder, function: CodeBlock.Builder) {
typeHolder.entries.forEachIndexed { index, info ->
addDecoderStatementForType(function, locationArgs, index, info.second)
addDecoderStatementForType(function, index, info.second)
}
}

Expand Down Expand Up @@ -205,10 +205,7 @@ object AbiParser {
internal fun generateParameterDecoderCode(parameters: List<ParameterJson>): CodeBlock {
val codeBlock = CodeBlock.builder()

//Generate decodings
val locationArgs = ArrayList<Pair<String, TypeHolder>>()
generateStaticArgDecoding(parameters, codeBlock, locationArgs)
generateDynamicArgDecoding(locationArgs, codeBlock)
generateStaticArgDecoding(parameters, codeBlock)

return codeBlock.build()
}
Expand All @@ -227,37 +224,39 @@ object AbiParser {
return returnContainerBuilder.primaryConstructor(returnContainerConstructor.build()).build()
}

private fun generateStaticArgDecoding(parameters: List<ParameterJson>, function: CodeBlock.Builder, locationArgs: MutableCollection<Pair<String, TypeHolder>>) {
private fun generateStaticArgDecoding(parameters: List<ParameterJson>, function: CodeBlock.Builder) {
parameters.forEachIndexed { index, outputJson ->
val className = mapType(outputJson, context)
addDecoderStatementForType(function, locationArgs, index, className)
addDecoderStatementForType(function, index, className)
}
}

private fun addDecoderStatementForType(function: CodeBlock.Builder, locationArgs: MutableCollection<Pair<String, TypeHolder>>, index: Int, className: TypeHolder) {
when {
isSolidityDynamicType(className) -> {
locationArgs.add("$index" to className)
function.addStatement("$DECODER_VAR_PARTITIONS_NAME.consume()")
}
isSolidityArray(className) -> {
val format = buildArrayDecoder(className, forceStatic = true)
function.addStatement("val $DECODER_VAR_ARG_PREFIX$index = ${format.first}.decode(%L)", *format.second.toTypedArray(), DECODER_VAR_PARTITIONS_NAME)
}
else -> function.addStatement("val $DECODER_VAR_ARG_PREFIX$index = %1T.DECODER.decode($DECODER_VAR_PARTITIONS_NAME)", className.toTypeName())
private fun addDecoderStatementForType(function: CodeBlock.Builder, index: Int, className: TypeHolder) {
val dynamicValName = "$DECODER_VAR_ARG_PREFIX$index"
val source = if (isSolidityDynamicType(className)) {
val dynamicValOffsetName = "$dynamicValName$DECODER_VAR_ARG_OFFSET_SUFFIX"
function.addStatement(
"val $dynamicValOffsetName = %T(%L.consume(), 16).intValueExact()",
BigInteger::class.asClassName(), DECODER_VAR_PARTITIONS_NAME
)
"%L.subData(%L)" to mutableListOf(DECODER_VAR_PARTITIONS_NAME, dynamicValOffsetName)
} else {
DECODER_VAR_PARTITIONS_NAME to mutableListOf()
}
}

private fun generateDynamicArgDecoding(locationArgs: List<Pair<String, TypeHolder>>, function: CodeBlock.Builder) {
locationArgs.forEach { (argIndex, type) ->
val dynamicValName = "$DECODER_VAR_ARG_PREFIX$argIndex"

if (isSolidityArray(type)) {
val format = buildArrayDecoder(type)
function.addStatement("val $dynamicValName = ${format.first}.decode(%L)", *format.second.toTypedArray(), DECODER_VAR_PARTITIONS_NAME)
} else {
function.addStatement("val $dynamicValName = %1T.DECODER.decode(%2L)", type.toTypeName(), DECODER_VAR_PARTITIONS_NAME)
}
if (isSolidityArray(className)) {
val format = buildArrayDecoder(className)
function
.addStatement(
"val $dynamicValName = ${format.first}.decode(${source.first})",
*format.second.toTypedArray(), *source.second.toTypedArray()
)
} else {
function
.addStatement(
"val $dynamicValName = %T.DECODER.decode(${source.first})",
className.toTypeName(), *source.second.toTypedArray()
)
}
}

Expand Down
11 changes: 6 additions & 5 deletions bivrost-abi-parser/src/main/kotlin/pm/gnosis/EventParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ internal object EventParser {
private const val EVENT_ARGUMENTS_CLASS_NAME = "Arguments"
private const val DECODE_FUN_NAME = "decode"
private const val TOPIC_ARG_NAME = "topics"
private const val TOPICS_PARTITION_DATA_NAME = "topicsSource"

internal fun generateEventObjects(): TypeSpec? {
val eventsObject = TypeSpec.objectBuilder(ROOT_OBJECT_NAME)
Expand Down Expand Up @@ -85,17 +84,19 @@ internal object EventParser {
private fun generateTopicsDecoderCodeBlock(indexedParameters: List<ParameterJson>, isAnonymous: Boolean): CodeBlock {
val codeBlock = CodeBlock.builder()
codeBlock.addStatement("// Decode topics")
codeBlock.addStatement("val $TOPICS_PARTITION_DATA_NAME = %1T($TOPIC_ARG_NAME)", SolidityBase.PartitionData::class)
if (!isAnonymous) {
codeBlock.addStatement("if ($TOPICS_PARTITION_DATA_NAME.consume() != $EVENT_ID_PROPERTY_NAME) throw %1T(\"topics[0] does not match event id\")", IllegalArgumentException::class)
codeBlock.addStatement("if ($TOPIC_ARG_NAME.first() != $EVENT_ID_PROPERTY_NAME) throw %1T(\"topics[0] does not match event id\")", IllegalArgumentException::class)
}

indexedParameters.forEachIndexed { index, topic ->
val typeHolder = mapType(topic, context)
if (typeHolder.isHashTopic(topic)) {
codeBlock.addStatement("val t${index + 1} = $TOPICS_PARTITION_DATA_NAME.consume()")
codeBlock.addStatement("val t${index + 1} = $TOPIC_ARG_NAME[${index + 1}]")
} else {
codeBlock.addStatement("val t${index + 1} = %1T.DECODER.decode($TOPICS_PARTITION_DATA_NAME)", typeHolder.toTypeName())
val sourceName = "source${index + 1}"
codeBlock
.addStatement("val $sourceName = %1T.of($TOPIC_ARG_NAME[${index + 1}])", SolidityBase.PartitionData::class)
.addStatement("val t${index + 1} = %1T.DECODER.decode(%2L)", typeHolder.toTypeName(), sourceName)
}
}

Expand Down
20 changes: 8 additions & 12 deletions bivrost-abi-parser/src/test/kotlin/pm/gnosis/AbiParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -231,26 +231,22 @@ class AbiParserTest {
Solidity.UInt256.DECODER.decode(testData).value,
BigInteger("123", 16))

// Consume location of dynamic uint32 array (we don't need it)
testData.consume()
// Decode uint32[]
val uint32Offset = BigInteger(testData.consume(), 16).intValueExact()
assertEquals(
SolidityBase.Vector.Decoder(Solidity.UInt32.DECODER).decode(testData.subData(uint32Offset)).items,
listOf(Solidity.UInt32(BigInteger("456", 16)), Solidity.UInt32(BigInteger("789", 16))))

// Decode bytes10
Assert.assertArrayEquals(
Solidity.Bytes10.DECODER.decode(testData).bytes,
"1234567890".toByteArray())

// Consume location of bytes (we don't need it)
testData.consume()

// Decode dynamic uint32 array
assertEquals(
SolidityBase.Vector.Decoder(Solidity.UInt32.DECODER).decode(testData).items,
listOf(Solidity.UInt32(BigInteger("456", 16)), Solidity.UInt32(BigInteger("789", 16))))

// Decode bytes
val bytesOffset = BigInteger(testData.consume(), 16).intValueExact()
Assert.assertArrayEquals(
Solidity.Bytes.DECODER.decode(testData).items,
"Hello, world!".toByteArray())
Solidity.Bytes.DECODER.decode(testData.subData(bytesOffset)).items,
"Hello, world!".toByteArray())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package expected

import java.math.BigInteger
import kotlin.String
import pm.gnosis.model.Solidity
import pm.gnosis.model.SolidityBase
Expand All @@ -14,8 +15,8 @@ class Abi7 {
val source = SolidityBase.PartitionData.of(data)

// Add decoders
source.consume()
val arg0 = Solidity.Bytes.DECODER.decode(source)
val arg0Offset = BigInteger(source.consume(), 16).intValueExact()
val arg0 = Solidity.Bytes.DECODER.decode(source.subData(arg0Offset))

return Return(arg0)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package expected

import java.math.BigInteger
import kotlin.String
import pm.gnosis.model.Solidity
import pm.gnosis.model.SolidityBase
Expand All @@ -14,8 +15,8 @@ class Abi8 {
val source = SolidityBase.PartitionData.of(data)

// Add decoders
source.consume()
val arg0 = Solidity.Bytes.DECODER.decode(source)
val arg0Offset = BigInteger(source.consume(), 16).intValueExact()
val arg0 = Solidity.Bytes.DECODER.decode(source.subData(arg0Offset))

return Return(arg0)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package expected

import expected.arrays.Array5
import expected.arrays.Array7
import java.math.BigInteger
import kotlin.Boolean
import kotlin.String
import pm.gnosis.model.Solidity
Expand All @@ -18,8 +19,8 @@ class Abi9 {

// Add decoders
val arg0 = TupleB.DECODER.decode(source)
source.consume()
val arg1 = SolidityBase.Vector.Decoder(TupleB.DECODER).decode(source)
val arg1Offset = BigInteger(source.consume(), 16).intValueExact()
val arg1 = SolidityBase.Vector.Decoder(TupleB.DECODER).decode(source.subData(arg1Offset))

return Return(arg0, arg1)
}
Expand All @@ -28,10 +29,10 @@ class Abi9 {
val source = SolidityBase.PartitionData.of(data)

// Add decoders
source.consume()
source.consume()
val arg0 = SolidityBase.Vector.Decoder(TupleA.DECODER).decode(source)
val arg1 = SolidityBase.Vector.Decoder(SolidityBase.Vector.Decoder(Array7.Decoder(Array5.Decoder(Solidity.UInt256.DECODER)))).decode(source)
val arg0Offset = BigInteger(source.consume(), 16).intValueExact()
val arg0 = SolidityBase.Vector.Decoder(TupleA.DECODER).decode(source.subData(arg0Offset))
val arg1Offset = BigInteger(source.consume(), 16).intValueExact()
val arg1 = SolidityBase.Vector.Decoder(SolidityBase.Vector.Decoder(Array7.Decoder(Array5.Decoder(Solidity.UInt256.DECODER)))).decode(source.subData(arg1Offset))

return Arguments(arg0, arg1)
}
Expand All @@ -53,8 +54,8 @@ class Abi9 {
override fun decode(source: SolidityBase.PartitionData): TupleA {
val arg0 = Solidity.UInt256.DECODER.decode(source)
val arg1 = Solidity.UInt256.DECODER.decode(source)
source.consume()
val arg2 = SolidityBase.Vector.Decoder(SolidityBase.Vector.Decoder(Array7.Decoder(Array5.Decoder(Solidity.UInt256.DECODER)))).decode(source)
val arg2Offset = BigInteger(source.consume(), 16).intValueExact()
val arg2 = SolidityBase.Vector.Decoder(SolidityBase.Vector.Decoder(Array7.Decoder(Array5.Decoder(Solidity.UInt256.DECODER)))).decode(source.subData(arg2Offset))
return TupleA(arg0, arg1, arg2)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class Abi11 {

fun decode(topics: List<String>): Arguments {
// Decode topics
val topicsSource = SolidityBase.PartitionData(topics)
if (topicsSource.consume() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")
val t1 = Solidity.UInt256.DECODER.decode(topicsSource)
if (topics.first() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")
val source1 = SolidityBase.PartitionData.of(topics[1])
val t1 = Solidity.UInt256.DECODER.decode(source1)
return Arguments(t1)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ class Abi12 {

fun decode(topics: List<String>, data: String): Arguments {
// Decode topics
val topicsSource = SolidityBase.PartitionData(topics)
if (topicsSource.consume() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")
if (topics.first() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")

// Decode data
val source = SolidityBase.PartitionData.of(data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ class Abi13 {

fun decode(topics: List<String>): Arguments {
// Decode topics
val topicsSource = SolidityBase.PartitionData(topics)
if (topicsSource.consume() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")
val t1 = topicsSource.consume()
val t2 = topicsSource.consume()
val t3 = topicsSource.consume()
if (topics.first() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")
val t1 = topics[1]
val t2 = topics[2]
val t3 = topics[3]
return Arguments(t1, t2, t3)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package expected

import java.lang.IllegalArgumentException
import java.math.BigInteger
import kotlin.Boolean
import kotlin.String
import kotlin.collections.List
Expand All @@ -14,16 +15,15 @@ class Abi14 {

fun decode(topics: List<String>, data: String): Arguments {
// Decode topics
val topicsSource = SolidityBase.PartitionData(topics)
if (topicsSource.consume() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")
if (topics.first() != EVENT_ID) throw IllegalArgumentException("topics[0] does not match event id")

// Decode data
val source = SolidityBase.PartitionData.of(data)
source.consume()
source.consume()
val arg0Offset = BigInteger(source.consume(), 16).intValueExact()
val arg0 = Solidity.Bytes.DECODER.decode(source.subData(arg0Offset))
val arg1Offset = BigInteger(source.consume(), 16).intValueExact()
val arg1 = Solidity.String.DECODER.decode(source.subData(arg1Offset))
val arg2 = TupleA.DECODER.decode(source)
val arg0 = Solidity.Bytes.DECODER.decode(source)
val arg1 = Solidity.String.DECODER.decode(source)
return Arguments(arg0, arg1, arg2)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class Abi15 {

fun decode(topics: List<String>): Arguments {
// Decode topics
val topicsSource = SolidityBase.PartitionData(topics)
val t1 = Solidity.UInt256.DECODER.decode(topicsSource)
val source1 = SolidityBase.PartitionData.of(topics[1])
val t1 = Solidity.UInt256.DECODER.decode(source1)
return Arguments(t1)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"contractName": "Abi16",
"abi": [
{
"constant": true,
"inputs": [
{
"name": "c",
"type": "tuple",
"components": [
{
"name": "bytesVar",
"type": "bytes"
},
{
"name": "stringVar",
"type": "string"
}
]
}
],
"name": "malformed",
"outputs": [],
"payable": false,
"type": "function"
}
]
}
Loading

0 comments on commit 5de310b

Please sign in to comment.