Skip to content

Commit

Permalink
WA-42: Test generated code (#43)
Browse files Browse the repository at this point in the history
  • Loading branch information
fmrsabino authored and rmeissner committed Nov 14, 2017
1 parent db29a77 commit f367a62
Show file tree
Hide file tree
Showing 38 changed files with 870 additions and 8 deletions.
4 changes: 2 additions & 2 deletions bivrost-abi-parser/src/main/kotlin/pm/gnosis/AbiParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ object AbiParser {
val kotlinFile = FileSpec.builder(packageName, abiRoot.contractName)

kotlinClass.addTypes(generateFunctionObjects())
EventParser.generateEventObjects()?.let { kotlinClass.addType(it) }
kotlinClass.addTypes(generateTupleObjects())
kotlinClass.addType(EventParser.generateEventObjects())

val build = kotlinFile.addType(kotlinClass.build()).indent(INDENTATION).build()
output.mkdirs()
Expand Down Expand Up @@ -84,7 +84,7 @@ object AbiParser {

private fun generateFunctionObjects() =
context.root.abi
.filter { it.type == "function" }
.filter { it.type == "function" && it.name.isNotBlank() }
.groupBy { it.name }
.flatMap { (_, value) -> value.map { Pair(it, value.size > 1) } }
.map { (functionJson, useMethodId) ->
Expand Down
15 changes: 10 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 @@ -16,8 +16,11 @@ internal object EventParser {
private const val TOPIC_ARG_NAME = "topics"
private const val TOPICS_PARTITION_DATA_NAME = "topicsSource"

internal fun generateEventObjects(): TypeSpec {
internal fun generateEventObjects(): TypeSpec? {
val eventsObject = TypeSpec.objectBuilder(ROOT_OBJECT_NAME)
if (!context.root.abi.any { it.type == ABI_SPEC_EVENT_TYPE }) {
return null
}

context.root.abi.filter { it.type == ABI_SPEC_EVENT_TYPE }
.map { generateEventObject(it) }
Expand All @@ -27,14 +30,16 @@ internal object EventParser {
}

private fun generateEventObject(abiElementJson: AbiElementJson): TypeSpec {
val eventObject = TypeSpec.objectBuilder(abiElementJson.name)
val eventObject = TypeSpec.objectBuilder(abiElementJson.name.capitalize())

val eventId = "${abiElementJson.name}(${abiElementJson.inputs.joinToString(",") { it.type }})".keccak256()
eventObject.addProperty(PropertySpec.builder(EVENT_ID_PROPERTY_NAME, String::class, KModifier.CONST).initializer("\"$eventId\"").build())

val holder = generateEventParameterHolder(EVENT_ARGUMENTS_CLASS_NAME, abiElementJson.inputs)
eventObject.addFunction(generateEventDecoder(abiElementJson, holder.name!!))
eventObject.addType(holder)
if (abiElementJson.inputs.isNotEmpty()) {
val holder = generateEventParameterHolder(EVENT_ARGUMENTS_CLASS_NAME, abiElementJson.inputs)
eventObject.addFunction(generateEventDecoder(abiElementJson, holder.name!!))
eventObject.addType(holder)
}

return eventObject.build()
}
Expand Down
93 changes: 93 additions & 0 deletions bivrost-abi-parser/src/test/kotlin/pm/gnosis/AbiGeneratorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package pm.gnosis

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File

class AbiGeneratorTest {
@Rule
@JvmField
val folder = TemporaryFolder()

private lateinit var generatedFolder: File

companion object {
const val PACKAGE_NAME = "expected"
val PATH = PACKAGE_NAME.replace('.', File.separatorChar)
}

@Before
fun setup() {
generatedFolder = folder.newFolder("testGeneratedCode")
}

@Test
fun testGeneratedCode() {
val testsFolder = File(javaClass.classLoader.getResource("automatic_tests").toURI())
for (testFolder in testsFolder.listFiles().sortedBy { it.name }) {
testAbi(testFolder)
}
}

private fun testAbi(testFolder: File) {
println("Testing ${testFolder.nameWithoutExtension}")

generatedFolder.deleteRecursively()
val abisFolder = File(testFolder, "abis")

val arraysMap = AbiParser.ArraysMap(PACKAGE_NAME)
abisFolder.listFiles().forEach { jsonAbi ->
val jsonAbiContents = readAllFromFile(jsonAbi)
AbiParser.generateWrapper(PACKAGE_NAME, jsonAbiContents, generatedFolder, arraysMap)
}

arraysMap.generate(generatedFolder)

val generatedRootFolder = File(generatedFolder, PATH)
assertTrue("Root folder was not generated", generatedRootFolder.exists() && generatedRootFolder.isDirectory)

val expectedFolder = File(testFolder, PATH)
checkGeneratedFolder(generatedRootFolder, expectedFolder)
checkGeneratedFileList(generatedRootFolder, expectedFolder)
}

private fun checkGeneratedFileList(generatedFolder: File, expectedFolder: File) {
expectedFolder.listFiles().forEach {
val generated = File(generatedFolder, it.name)
assertTrue("$it was not generated!", generated.exists() &&
generated.isDirectory == it.isDirectory && generated.isFile == it.isFile)
if (it.isDirectory) {
checkGeneratedFileList(generated, it)
}
}
}

private fun checkGeneratedFolder(generatedFolder: File, expectedFolder: File) {
generatedFolder.listFiles().forEach {
if (it.isDirectory) {
val target = File(expectedFolder, it.name)
assertTrue("$it was not expected as a generated folder!", target.exists() && target.isDirectory)
checkGeneratedFolder(it, target)
} else {
checkGeneratedFile(it, expectedFolder)
}
}
}

private fun checkGeneratedFile(generatedFile: File, expectedFolder: File) {
val expectedFile = File(expectedFolder, generatedFile.name)
assertTrue("$generatedFile was not expected to be generated!", expectedFile.exists() && expectedFile.isFile)

val generatedContent = readAllFromFile(generatedFile)
val expectedContent = readAllFromFile(expectedFile)

assertEquals("${generatedFile.name} does not match the expected file",
expectedContent, generatedContent)
}

private fun readAllFromFile(file: File) = file.bufferedReader().use { it.readText() }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"contract_name": "EmptyContract",
"abi": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package expected

class EmptyContract
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"contract_name": "Abi10",
"abi": [
{
"constant": true,
"inputs": [
],
"name": "executed",
"outputs": [
],
"payable": false,
"type": "event"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package expected

import kotlin.String

class Abi10 {
object Events {
object Executed {
const val EVENT_ID: String = "31a38c898682b31869cf766157e0c918f2026132f81da53d2220a90addbdd887"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"contract_name": "Abi11",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "transactionId",
"type": "uint256"
}
],
"name": "Submission",
"type": "event"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package expected

import java.lang.IllegalArgumentException
import kotlin.String
import kotlin.collections.List
import pm.gnosis.model.Solidity
import pm.gnosis.model.SolidityBase

class Abi11 {
object Events {
object Submission {
const val EVENT_ID: String = "c0ba8fe4b176c1714197d43b9cc6bcf797a4a7461c5fe8d0ef6e184ae7601e51"

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)
return Arguments(t1)
}

data class Arguments(val transactionid: Solidity.UInt256)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"contract_name": "Abi12",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "transactionId",
"type": "uint256"
}
],
"name": "Submission",
"type": "event"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package expected

import java.lang.IllegalArgumentException
import kotlin.String
import kotlin.collections.List
import pm.gnosis.model.Solidity
import pm.gnosis.model.SolidityBase

class Abi12 {
object Events {
object Submission {
const val EVENT_ID: String = "c0ba8fe4b176c1714197d43b9cc6bcf797a4a7461c5fe8d0ef6e184ae7601e51"

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")

// Decode data
val source = SolidityBase.PartitionData.of(data)
val arg0 = Solidity.UInt256.DECODER.decode(source)
return Arguments(arg0)
}

data class Arguments(val transactionid: Solidity.UInt256)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"contract_name": "Abi13",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "bytes",
"type": "bytes"
},
{
"indexed": true,
"name": "string",
"type": "string"
},
{
"indexed": true,
"type": "tuple",
"name": "tuple",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
}
],
"name": "Submission",
"type": "event"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package expected

import java.lang.IllegalArgumentException
import kotlin.Boolean
import kotlin.String
import kotlin.collections.List
import pm.gnosis.model.Solidity
import pm.gnosis.model.SolidityBase

class Abi13 {
object Events {
object Submission {
const val EVENT_ID: String = "0c5212e9d002fa3e0c9bd8c78b6d4df3e94f4e956761bd40f0859c979600a2e7"

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()
return Arguments(t1, t2, t3)
}

data class Arguments(
val bytesHash: String,
val stringHash: String,
val tupleHash: String
)
}
}

data class TupleA(val x: Solidity.UInt256, val y: Solidity.UInt256) : SolidityBase.StaticType {
override fun encode(): String = SolidityBase.encodeFunctionArguments(x, y)

class Decoder : SolidityBase.TypeDecoder<TupleA> {
override fun isDynamic(): Boolean = false
override fun decode(source: SolidityBase.PartitionData): TupleA {
val arg0 = Solidity.UInt256.DECODER.decode(source)
val arg1 = Solidity.UInt256.DECODER.decode(source)
return TupleA(arg0, arg1)
}
}
companion object {
val DECODER: Decoder = Decoder()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"contract_name": "Abi14",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"name": "bytes",
"type": "bytes"
},
{
"indexed": false,
"name": "string",
"type": "string"
},
{
"indexed": false,
"type": "tuple",
"name": "tuple",
"components": [
{
"name": "x",
"type": "uint256"
},
{
"name": "y",
"type": "uint256"
}
]
}
],
"name": "Submission",
"type": "event"
}
]
}
Loading

0 comments on commit f367a62

Please sign in to comment.