Skip to content

Commit

Permalink
Add 1inch transaction decorations
Browse files Browse the repository at this point in the history
  • Loading branch information
omurovch committed Jul 15, 2021
1 parent 5186061 commit e424ef9
Show file tree
Hide file tree
Showing 16 changed files with 483 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.horizontalsystems.ethereumkit.sample.core.Erc20Adapter
import io.horizontalsystems.ethereumkit.sample.core.EthereumAdapter
import io.horizontalsystems.ethereumkit.sample.core.TransactionRecord
import io.horizontalsystems.hdwalletkit.Mnemonic
import io.horizontalsystems.oneinchkit.OneInchKit
import io.horizontalsystems.uniswapkit.UniswapKit
import io.horizontalsystems.uniswapkit.models.SwapData
import io.horizontalsystems.uniswapkit.models.Token
Expand Down Expand Up @@ -75,6 +76,7 @@ class MainViewModel : ViewModel() {

ethereumKit.addDecorator(Erc20Kit.decorator(ethereumKit))
ethereumKit.addDecorator(UniswapKit.decorator(ethereumKit))
ethereumKit.addDecorator(OneInchKit.decorator(ethereumKit))

updateBalance()
updateErc20Balance()
Expand Down Expand Up @@ -236,7 +238,7 @@ class MainViewModel : ViewModel() {
}

private fun updateTransactionList() {
val list = when(showTxType){
val list = when (showTxType) {
ShowTxType.Eth -> ethTxs
ShowTxType.Erc20 -> erc20Txs
}
Expand Down Expand Up @@ -331,7 +333,7 @@ class MainViewModel : ViewModel() {
}

fun filterTransactions(ethTx: Boolean) {
if (ethTx){
if (ethTx) {
updateEthTransactions()
showTxType = ShowTxType.Eth
} else {
Expand Down Expand Up @@ -454,7 +456,7 @@ class MainViewModel : ViewModel() {

}

enum class ShowTxType{
enum class ShowTxType {
Eth, Erc20
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import io.horizontalsystems.ethereumkit.spv.core.toBigInteger
import io.horizontalsystems.ethereumkit.spv.core.toInt
import java.math.BigInteger
import kotlin.math.max
import kotlin.reflect.KClass

object ContractMethodHelper {
fun getMethodId(methodSignature: String): ByteArray {
Expand Down Expand Up @@ -39,7 +38,9 @@ object ContractMethodHelper {
return data + arraysData
}

fun decodeABI(inputArguments: ByteArray, argumentTypes: List<KClass<out Any>>): List<Any> {
data class StructParameter(val argumentTypes: List<Any>)

fun decodeABI(inputArguments: ByteArray, argumentTypes: List<Any>): List<Any> {
var position = 0
val parsedArguments = mutableListOf<Any>()
argumentTypes.forEach { type ->
Expand All @@ -60,18 +61,41 @@ object ContractMethodHelper {
val byteArray: ByteArray = parseByteArray(arrayPosition, inputArguments)
parsedArguments.add(byteArray)
}
Bytes32Array::class -> {
val arrayPosition = inputArguments.copyOfRange(position, position + 32).toInt()
val bytes32Array = parseBytes32Array(arrayPosition, inputArguments)
parsedArguments.add(bytes32Array)
}
is StructParameter -> {
val argumentsPosition = inputArguments.copyOfRange(position, position + 32).toInt()
val structParameterData = inputArguments.copyOfRange(argumentsPosition, inputArguments.size)
val structParameter = decodeABI(structParameterData, type.argumentTypes)
parsedArguments.add(structParameter)
}
}
position += 32
}

return parsedArguments
}

private fun parseBytes32Array(startPosition: Int, inputArguments: ByteArray): Bytes32Array {
val dataStartPosition = startPosition + 32
val size = inputArguments.copyOfRange(startPosition, dataStartPosition).toInt()
val array: Array<ByteArray> = Array(size) { byteArrayOf() }

for (i in 0 until size) {
array[i] = inputArguments.copyOfRange(dataStartPosition + 32 * i, dataStartPosition + 32 * (i + 1))
}

return Bytes32Array(array)
}

private fun parseAddress(address: ByteArray): Address {
return Address(address.copyOfRange(address.size - 20, address.size))
}

private fun parseByteArray(startPosition: Int, inputArguments: ByteArray) : ByteArray {
private fun parseByteArray(startPosition: Int, inputArguments: ByteArray): ByteArray {
val dataStartPosition = startPosition + 32
val size = inputArguments.copyOfRange(startPosition, dataStartPosition).toInt()
return inputArguments.copyOfRange(dataStartPosition, dataStartPosition + size)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.horizontalsystems.ethereumkit.contracts


// Class for solidity type bytes32[]
class Bytes32Array(val array: Array<ByteArray>)
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class ContractCallDecorator(val address: Address) : IDecorator {
private var methods = mutableMapOf<ByteArray, RecognizedContractMethod>()

init {
addMethod("deposit", "deposit(uint256)", listOf(BigInteger::class))
addMethod("tradeWithHintAndFee", "tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)",
addMethod("Deposit", "deposit(uint256)", listOf(BigInteger::class))
addMethod("TradeWithHintAndFee", "tradeWithHintAndFee(address,uint256,address,address,uint256,uint256,address,uint256,bytes)",
listOf(Address::class, BigInteger::class, Address::class, Address::class, BigInteger::class, BigInteger::class, Address::class, BigInteger::class, ByteArray::class))
}

Expand Down
10 changes: 3 additions & 7 deletions oneinchkit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1")

implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
Expand All @@ -43,5 +38,6 @@ dependencies {
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.google.code.gson:gson:2.8.6'

api project(':ethereumkit')
implementation project(':ethereumkit')
implementation project(':erc20kit')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.horizontalsystems.oneinchkit

import io.horizontalsystems.ethereumkit.core.EthereumKit
import io.horizontalsystems.ethereumkit.core.toHexString
import io.horizontalsystems.ethereumkit.models.FullTransaction
import io.horizontalsystems.oneinchkit.decorations.OneInchMethodDecoration
import io.horizontalsystems.oneinchkit.decorations.OneInchSwapMethodDecoration
import io.reactivex.disposables.CompositeDisposable
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

class OneInchInternalTransactionSyncer(
private val evmKit: EthereumKit
) : CoroutineScope {

private val disposables = CompositeDisposable()
private var syncingTransactions = mutableMapOf<String, Int>()
private val maxRetryCount = 3
private val delayTime = 10 * 1000L // seconds

private val job = Job()

init {
evmKit.allTransactionsFlowable
.subscribe { transactions ->
launch {
transactions.forEach { fullTransaction ->
handle(fullTransaction)
}
}
}
.let {
disposables.add(it)
}
}

override val coroutineContext: CoroutineContext = job + Dispatchers.IO

private suspend fun handle(fullTransaction: FullTransaction) {
if (fullTransaction.internalTransactions.isEmpty()) {

val decoration = fullTransaction.mainDecoration
if (decoration is OneInchSwapMethodDecoration
&& decoration.toToken is OneInchMethodDecoration.Token.EvmCoin
&& fullTransaction.transaction.from == evmKit.receiveAddress
&& decoration.recipient != evmKit.receiveAddress) {

val transaction = fullTransaction.transaction
val count = syncingTransactions[transaction.hash.toHexString()] ?: 0

if (count < maxRetryCount) {
delay(delayTime)
syncingTransactions[transaction.hash.toHexString()] = count + 1
evmKit.syncInternalTransactions(transaction)
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package io.horizontalsystems.oneinchkit
import com.google.gson.annotations.SerializedName
import io.horizontalsystems.ethereumkit.core.EthereumKit
import io.horizontalsystems.ethereumkit.core.EthereumKit.NetworkType
import io.horizontalsystems.ethereumkit.core.IDecorator
import io.horizontalsystems.ethereumkit.core.toHexString
import io.horizontalsystems.ethereumkit.models.Address
import io.horizontalsystems.oneinchkit.contracts.OneInchContractMethodFactories
import io.horizontalsystems.oneinchkit.decorations.OneInchTransactionDecorator
import io.reactivex.Single
import java.math.BigInteger
import java.util.*

class OneInchKit(
private val evmKit: EthereumKit,
private val service: OneInchService
private val service: OneInchService,
private val internalTransactionSyncer: OneInchInternalTransactionSyncer
) {

val smartContractAddress: Address = when (evmKit.networkType) {
Expand Down Expand Up @@ -62,7 +66,12 @@ class OneInchKit(

fun getInstance(evmKit: EthereumKit): OneInchKit {
val service = OneInchService(evmKit.networkType)
return OneInchKit(evmKit, service)
val internalTransactionSyncer = OneInchInternalTransactionSyncer(evmKit)
return OneInchKit(evmKit, service, internalTransactionSyncer)
}

fun decorator(evmKit: EthereumKit): IDecorator {
return OneInchTransactionDecorator(evmKit.receiveAddress, OneInchContractMethodFactories)
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.horizontalsystems.oneinchkit.contracts

import io.horizontalsystems.ethereumkit.contracts.ContractMethodFactories

object OneInchContractMethodFactories : ContractMethodFactories() {

init {
val swapContractMethodFactories = listOf(UnoswapMethodFactory())
registerMethodFactories(swapContractMethodFactories)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.horizontalsystems.oneinchkit.contracts

import io.horizontalsystems.ethereumkit.contracts.ContractMethod
import io.horizontalsystems.ethereumkit.models.Address
import java.math.BigInteger

class SwapMethod(
val caller: Address,
val swapDescription: SwapDescription,
val data: ByteArray
) : ContractMethod() {

override val methodSignature = Companion.methodSignature

override fun getArguments() = listOf(caller, swapDescription, data)

data class SwapDescription(
val srcToken: Address,
val dstToken: Address,
val srcReceiver: Address,
val dstReceiver: Address,
val amount: BigInteger,
val minReturnAmount: BigInteger,
val flags: BigInteger,
val permit: ByteArray
)

companion object {
val methodSignature = "swap(address,(address,address,address,address,uint256,uint256,uint256,bytes),bytes)"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.horizontalsystems.oneinchkit.contracts

import io.horizontalsystems.ethereumkit.contracts.ContractMethod
import io.horizontalsystems.ethereumkit.contracts.ContractMethodFactory
import io.horizontalsystems.ethereumkit.contracts.ContractMethodHelper
import io.horizontalsystems.ethereumkit.models.Address
import java.math.BigInteger

class SwapMethodFactory : ContractMethodFactory {

override val methodId = ContractMethodHelper.getMethodId(SwapMethod.methodSignature)

override fun createMethod(inputArguments: ByteArray): ContractMethod {
val argumentTypes = listOf(
Address::class,
ContractMethodHelper.StructParameter(listOf(Address::class, Address::class, Address::class, Address::class, BigInteger::class, BigInteger::class, BigInteger::class, ByteArray::class)),
ByteArray::class
)
val parsedArguments = ContractMethodHelper.decodeABI(inputArguments, argumentTypes)

val caller = parsedArguments[0] as Address
val swapDescriptionArguments = parsedArguments[1] as List<*>

val srcToken = swapDescriptionArguments[0] as Address
val dstToken = swapDescriptionArguments[1] as Address
val srcReceiver = swapDescriptionArguments[2] as Address
val dstReceiver = swapDescriptionArguments[3] as Address
val amount = swapDescriptionArguments[4] as BigInteger
val minReturnAmount = swapDescriptionArguments[5] as BigInteger
val flags = swapDescriptionArguments[6] as BigInteger
val permit = swapDescriptionArguments[7] as ByteArray

val swapDescription = SwapMethod.SwapDescription(srcToken, dstToken, srcReceiver, dstReceiver, amount, minReturnAmount, flags, permit)

val data = parsedArguments[2] as ByteArray

return SwapMethod(caller, swapDescription, data)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.horizontalsystems.oneinchkit.contracts

import io.horizontalsystems.ethereumkit.contracts.Bytes32Array
import io.horizontalsystems.ethereumkit.contracts.ContractMethod
import io.horizontalsystems.ethereumkit.models.Address
import java.math.BigInteger

class UnoswapMethod(
val srcToken: Address,
val amount: BigInteger,
val minReturn: BigInteger,
val params: Bytes32Array
) : ContractMethod() {

override val methodSignature = Companion.methodSignature

override fun getArguments() = listOf(srcToken, amount, minReturn, params)

companion object {
val methodSignature = "unoswap(address,uint256,uint256,bytes32[])"
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.horizontalsystems.oneinchkit.contracts

import io.horizontalsystems.ethereumkit.contracts.Bytes32Array
import io.horizontalsystems.ethereumkit.contracts.ContractMethod
import io.horizontalsystems.ethereumkit.contracts.ContractMethodFactory
import io.horizontalsystems.ethereumkit.contracts.ContractMethodHelper
import io.horizontalsystems.ethereumkit.models.Address
import java.math.BigInteger

class UnoswapMethodFactory : ContractMethodFactory {

override val methodId = ContractMethodHelper.getMethodId(UnoswapMethod.methodSignature)

override fun createMethod(inputArguments: ByteArray): ContractMethod {
val argumentTypes = listOf(Address::class, BigInteger::class, BigInteger::class, Bytes32Array::class)

val parsedArguments = ContractMethodHelper.decodeABI(inputArguments, argumentTypes)

val srcToken = parsedArguments[0] as Address
val amount = parsedArguments[1] as BigInteger
val minReturn = parsedArguments[2] as BigInteger
val params = parsedArguments[3] as Bytes32Array

return UnoswapMethod(srcToken, amount, minReturn, params)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.horizontalsystems.oneinchkit.decorations

import io.horizontalsystems.ethereumkit.decorations.ContractMethodDecoration
import io.horizontalsystems.ethereumkit.models.Address

abstract class OneInchMethodDecoration: ContractMethodDecoration() {

sealed class Token {
object EvmCoin : Token()
class Eip20(val address: Address) : Token()
}

}
Loading

0 comments on commit e424ef9

Please sign in to comment.