From a03fcb3c64062ed252bbb9699047565b08b1dd6d Mon Sep 17 00:00:00 2001 From: Hleb Albau Date: Fri, 18 May 2018 11:33:09 +0300 Subject: [PATCH] #161 Ethereum contract summary should use traces to calculate balances --method affected addresses shuld return same contracts as in delta --fix review comments --- .../search/model/ethereum/OperationTrace.kt | 20 +++++- .../cyber/search/model/ethereum/TxTrace.kt | 2 + .../delta/EthereumTxDeltaProcessor.kt | 30 ++++++-- .../delta/EthereumTxDeltaProcessorTest.kt | 70 +++++++++++++++++++ 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/common/src/main/kotlin/fund/cyber/search/model/ethereum/OperationTrace.kt b/common/src/main/kotlin/fund/cyber/search/model/ethereum/OperationTrace.kt index 970e75f8..f6abbeec 100644 --- a/common/src/main/kotlin/fund/cyber/search/model/ethereum/OperationTrace.kt +++ b/common/src/main/kotlin/fund/cyber/search/model/ethereum/OperationTrace.kt @@ -19,7 +19,25 @@ data class OperationTrace( @JsonDeserialize(using = OperationResultDeserializer::class) val result: OperationResult?, //null for reward and destroy contract operations val subtraces: List -) +) { + + /** + * Do not returns child contracts. + */ + fun contractsUsedInCurrentOp(): List { + + return when (operation) { + is CallOperation -> listOf(operation.from, operation.to) + is RewardOperation -> listOf(operation.address) + is DestroyContractOperation -> listOf(operation.contractToDestroy, operation.refundContract) + is CreateContractOperation -> { + if (result is CreateContractOperationResult) listOf(operation.from, result.address) + else listOf(operation.from) + } + } + } +} + class OperationsDeserializer : JsonDeserializer() { diff --git a/common/src/main/kotlin/fund/cyber/search/model/ethereum/TxTrace.kt b/common/src/main/kotlin/fund/cyber/search/model/ethereum/TxTrace.kt index d7e9da75..673b375a 100644 --- a/common/src/main/kotlin/fund/cyber/search/model/ethereum/TxTrace.kt +++ b/common/src/main/kotlin/fund/cyber/search/model/ethereum/TxTrace.kt @@ -7,6 +7,8 @@ data class TxTrace( val rootOperationTrace: OperationTrace ) { + fun isRootOperationFailed() = rootOperationTrace.result is ErroredOperationResult + fun getAllOperationsTraces() = allSuboperations(listOf(rootOperationTrace)) private fun allSuboperations(operations: List): List { diff --git a/contract-summary/ethereum/src/main/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessor.kt b/contract-summary/ethereum/src/main/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessor.kt index b81729aa..44f24c81 100644 --- a/contract-summary/ethereum/src/main/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessor.kt +++ b/contract-summary/ethereum/src/main/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessor.kt @@ -12,6 +12,7 @@ import fund.cyber.search.model.ethereum.EthereumTx import fund.cyber.search.model.ethereum.OperationResult import fund.cyber.search.model.ethereum.OperationTrace import fund.cyber.search.model.ethereum.RewardOperation +import fund.cyber.search.model.ethereum.TxTrace import fund.cyber.search.model.events.PumpEvent import org.apache.kafka.clients.consumer.ConsumerRecord import org.springframework.stereotype.Component @@ -40,9 +41,8 @@ class EthereumTxDeltaProcessor : DeltaProcessor>): Set { val allContracts: List = records.flatMap { record -> - val inContract = record.value().from - val outContract = (record.value().to ?: record.value().createdSmartContract)!! - return@flatMap listOf(inContract, outContract) + if (record.key() == PumpEvent.NEW_POOL_TX) return@flatMap emptyList() + return@flatMap record.value().trace!!.contractsChangingState() } return allContracts.filter { contract -> contract.isNotEmpty() }.toSet() @@ -76,7 +76,7 @@ class EthereumTxDeltaProcessor : DeltaProcessor): List { + return ops + .filter { op -> op.result !is ErroredOperationResult } + .flatMap { op -> + val contracts = op.contractsUsedInCurrentOp() + return@flatMap contracts + contractsChangingState(op.subtraces) + } +} + diff --git a/contract-summary/ethereum/src/test/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessorTest.kt b/contract-summary/ethereum/src/test/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessorTest.kt index 9c526f91..db127248 100644 --- a/contract-summary/ethereum/src/test/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessorTest.kt +++ b/contract-summary/ethereum/src/test/kotlin/fund/cyber/contract/ethereum/delta/EthereumTxDeltaProcessorTest.kt @@ -271,5 +271,75 @@ class EthereumTxDeltaProcessorTest { val actualDeltas = deltaProcessor.recordToDeltas(txRecord) Assertions.assertEquals(expectedDeltas, actualDeltas) } + + /** + * + * Tx -> Ok Call + * | -> Ok Call + * | -> Ok Call + * | -> Ok Create + * | -> Fail Call + * | -> Ok Destroy + * | -> Fail Call + * | -> Ok Call + */ + @Test + @DisplayName("affected addresses should returns same contracts as in record deltas") + fun testAffectedAddressShouldReturnsSameContractsAsDeltas() { + val subtraces13 = listOf( + destroyContractOperationTrace(contractToDelete = "1-1-3-1f", refundContract = "1-1-3-1t") + ) + val subtraces1 = listOf( + callOperationTrace(from = "1-1-1f", to = "1-1-1t"), + createContractOperationTrace(from = "1-1-2f", to = "1-1-2t"), + callOperationTrace(from = "1-1-3f", to = "1-1-3t", error = "e", subtraces = subtraces13) + ) + val subtraces = listOf( + callOperationTrace(from = "1-1f", to = "1-1t", subtraces = subtraces1), + callOperationTrace(from = "1-2f", to = "1-2t", error = "e"), + callOperationTrace(from = "1-3f", to = "1-3t") + ) + val txTrace = TxTrace( + callOperationTrace(from = "1f", to = "1t", subtraces = subtraces) + ) + + val txRecord = txRecord(trace = txTrace, value = ZERO) + + val expectedContracts = setOf("1f", "1t", "1-1f", "1-1t", "1-1-1f", "1-1-1t", "1-1-2f", "1-1-2t", "1-3f", "1-3t") + val affectedContracts = deltaProcessor.affectedContracts(listOf(txRecord)) + val deltasContracts = deltaProcessor.recordToDeltas(txRecord).map { d -> d.contract }.toSet() + + Assertions.assertEquals(expectedContracts, affectedContracts) + Assertions.assertEquals(expectedContracts, deltasContracts) + } + + /** + * + * Tx -> Ok Fail + * | -> Ok Call + * | -> Fail Call + * | -> Ok Call + */ + @Test + @DisplayName("affected addresses should returns same contracts as in record deltas for failed root tx") + fun testAffectedAddressShouldReturnsSameContractsAsDeltasForFailedRootTx() { + val subtraces = listOf( + callOperationTrace(from = "1-1f", to = "1-1t"), + callOperationTrace(from = "1-2f", to = "1-2t", error = "e"), + callOperationTrace(from = "1-3f", to = "1-3t") + ) + val txTrace = TxTrace( + callOperationTrace(from = "1f", to = "1t", subtraces = subtraces, error = "e") + ) + + val txRecord = txRecord(trace = txTrace, value = ZERO) + + val expectedContracts = setOf("1f", "1t") + val affectedContracts = deltaProcessor.affectedContracts(listOf(txRecord)) + val deltasContracts = deltaProcessor.recordToDeltas(txRecord).map { d -> d.contract }.toSet() + + Assertions.assertEquals(expectedContracts, affectedContracts) + Assertions.assertEquals(expectedContracts, deltasContracts) + } }