From b9c18938c790472d834a0a86598e00aa26568345 Mon Sep 17 00:00:00 2001 From: Kris Bitney Date: Tue, 15 Aug 2023 05:36:03 -0500 Subject: [PATCH] Add eth_accounts and eth_sign RPC methods (#149) * added eth_accounts and eth_sign methods to rpc provider * added eth_accounts and eth_sign methods to rpc provider * removed unnecessary blank line * bug fix in BaseEthereumRPC.accounts * bug fix in BaseEthereumRPC.sign --- .../org/kethereum/rpc/BaseEthereumRPC.kt | 24 ++++++++++++++---- .../kotlin/org/kethereum/rpc/EthereumRPC.kt | 3 ++- .../rpc/model/StringArrayResponse.kt | 6 +++++ .../org/kethereum/rpc/TheEthereumRPC.kt | 25 +++++++++++++++++++ 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 rpc/src/main/kotlin/org/kethereum/rpc/model/StringArrayResponse.kt diff --git a/rpc/src/main/kotlin/org/kethereum/rpc/BaseEthereumRPC.kt b/rpc/src/main/kotlin/org/kethereum/rpc/BaseEthereumRPC.kt index 8a5b6247..f74e1829 100644 --- a/rpc/src/main/kotlin/org/kethereum/rpc/BaseEthereumRPC.kt +++ b/rpc/src/main/kotlin/org/kethereum/rpc/BaseEthereumRPC.kt @@ -5,10 +5,7 @@ import com.squareup.moshi.Moshi import org.kethereum.extensions.BigIntegerAdapter import org.kethereum.extensions.hexToBigInteger import org.kethereum.extensions.toHexString -import org.kethereum.model.Address -import org.kethereum.model.ByteCode -import org.kethereum.model.ChainId -import org.kethereum.model.Transaction +import org.kethereum.model.* import org.kethereum.rpc.model.* import org.kethereum.rpc.model.BaseResponse import org.kethereum.rpc.model.BlockInformationResponse @@ -16,6 +13,7 @@ import org.kethereum.rpc.model.FeeHistoryResponse import org.kethereum.rpc.model.StringResultResponse import org.kethereum.rpc.model.TransactionResponse import org.komputing.khex.extensions.hexToByteArray +import org.komputing.khex.extensions.toHexString import org.komputing.khex.model.HexString import java.io.IOException import java.math.BigInteger @@ -34,6 +32,8 @@ open class BaseEthereumRPC(private val transport: RPCTransport) : EthereumRPC { private val feeHistoryAdapter: JsonAdapter = moshi.adapter(FeeHistoryResponse::class.java) + private val stringArrayAdapter: JsonAdapter = moshi.adapter(StringArrayResponse::class.java) + override fun stringCall(function: String, params: String): StringResultResponse? { return transport.call(function, params)?.let { string -> stringResultAdapter.fromJsonNoThrow(string) } } @@ -61,6 +61,21 @@ open class BaseEthereumRPC(private val transport: RPCTransport) : EthereumRPC { @Throws(EthereumRPCException::class) override fun chainId() = stringCall("eth_chainId")?.getBigIntegerFromStringResult()?.let { ChainId(it) } + @Throws(EthereumRPCException::class) + override fun accounts(): List
? { + val call = transport.call("eth_accounts", "")?.let { string -> + stringArrayAdapter.fromJson(string) + } + return call?.result?.map { Address(it) } + } + + @Throws(EthereumRPCException::class) + override fun sign(address: Address, message: ByteArray): SignatureData? = + stringCall("eth_sign", "\"${address.hex}\",\"${message.toHexString()}\"") + ?.throwOrString() + ?.let { SignatureData.fromHex(it) } + ?.let { SignatureData(r = it.r, s = it.s, v = it.v.plus(27.toBigInteger())) } + @Throws(EthereumRPCException::class) override fun getStorageAt(address: Address, position: String, block: String) = stringCall("eth_getStorageAt", "\"${address.hex}\",\"$position\",\"$block\"")?.throwOrString()?.let { HexString(it) } @@ -98,6 +113,5 @@ private fun StringResultResponse.throwOrString() = throwOnError().result!! @Throws(EthereumRPCException::class) private fun T.throwOnError() = error?.let { throw EthereumRPCException(it.message, it.code) } ?: this - @Throws(EthereumRPCException::class) private fun StringResultResponse.getBigIntegerFromStringResult() = HexString(throwOrString()).hexToBigInteger() diff --git a/rpc/src/main/kotlin/org/kethereum/rpc/EthereumRPC.kt b/rpc/src/main/kotlin/org/kethereum/rpc/EthereumRPC.kt index 171cebfa..b2bf46cf 100644 --- a/rpc/src/main/kotlin/org/kethereum/rpc/EthereumRPC.kt +++ b/rpc/src/main/kotlin/org/kethereum/rpc/EthereumRPC.kt @@ -12,7 +12,6 @@ interface EthereumRPC { fun getBlockByNumber(number: BigInteger): BlockInformation? fun getTransactionByHash(hash: String): SignedTransaction? - fun stringCall(function: String, params: String = ""): StringResultResponse? fun sendRawTransaction(data: String): String? fun blockNumber(): BigInteger? @@ -20,6 +19,8 @@ interface EthereumRPC { fun gasPrice(): BigInteger? fun clientVersion(): String? fun chainId(): ChainId? + fun accounts(): List
? + fun sign(address: Address, message: ByteArray): SignatureData? fun getStorageAt(address: Address, position: String, block: String = "latest"): HexString? fun getTransactionCount(address: Address, block: String = "latest"): BigInteger? fun getCode(address: Address, block: String = "latest"): ByteCode? diff --git a/rpc/src/main/kotlin/org/kethereum/rpc/model/StringArrayResponse.kt b/rpc/src/main/kotlin/org/kethereum/rpc/model/StringArrayResponse.kt new file mode 100644 index 00000000..f8cdd5e7 --- /dev/null +++ b/rpc/src/main/kotlin/org/kethereum/rpc/model/StringArrayResponse.kt @@ -0,0 +1,6 @@ +package org.kethereum.rpc.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class StringArrayResponse(val result: List) : BaseResponse() \ No newline at end of file diff --git a/rpc/src/test/kotlin/org/kethereum/rpc/TheEthereumRPC.kt b/rpc/src/test/kotlin/org/kethereum/rpc/TheEthereumRPC.kt index 49b3b37a..0dad8aba 100644 --- a/rpc/src/test/kotlin/org/kethereum/rpc/TheEthereumRPC.kt +++ b/rpc/src/test/kotlin/org/kethereum/rpc/TheEthereumRPC.kt @@ -8,6 +8,7 @@ import org.junit.Before import org.junit.jupiter.api.Test import org.kethereum.extensions.hexToBigInteger import org.kethereum.model.Address +import org.kethereum.model.SignatureData import org.kethereum.model.SignedTransaction import org.kethereum.rpc.model.BlockInformation import org.komputing.khex.extensions.hexToByteArray @@ -16,6 +17,7 @@ import java.math.BigInteger import java.math.BigInteger.TEN import java.math.BigInteger.ZERO import kotlin.test.assertFails +import kotlin.test.assertNotEquals class TheEthereumRPC { @@ -252,5 +254,28 @@ class TheEthereumRPC { .isEqualTo(HexString("0x76d4be3d62b9e6e6bb8c494c3228f4df31b5c20d8f892fe1d9d35f07afab3d73").hexToBigInteger()) } + @Test + fun accountsWorks() { + //language=JSON + val response = "{\"jsonrpc\":\"2.0\",\"id\":83,\"result\":[\"0x694b63b2e053a0c93074795a1025869576389b70\"]}\n" + server.enqueue(MockResponse().setBody(response)) + + val expected = listOf(Address("0x694b63b2e053a0c93074795a1025869576389b70")) + assertThat(tested.accounts()).isEqualTo(expected) + } + @Test + fun signWorks() { + //language=JSON + val response = "{\"jsonrpc\":\"2.0\",\"id\":83,\"result\":\"0xa4708243bf782c6769ed04d83e7192dbcf4fc131aa54fde9d889d8633ae39dab03d7babd2392982dff6bc20177f7d887e27e50848c851320ee89c6c63d18ca761c\"}\n" + server.enqueue(MockResponse().setBody(response)) + + val result: SignatureData? = tested.sign( + address = Address("0x694b63b2e053a0c93074795a1025869576389b70"), + message = "Hello World".encodeToByteArray() + ) + assertNotEquals(ZERO, result?.r) + assertNotEquals(ZERO, result?.s) + assertNotEquals(ZERO, result?.v) + } } \ No newline at end of file