diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/RPCTransaction.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/RPCTransaction.kt index eb5dac46..557c767e 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/RPCTransaction.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/RPCTransaction.kt @@ -18,7 +18,6 @@ import io.ethers.core.readListOf import io.ethers.core.readOrNull import io.ethers.core.types.transaction.ChainId import io.ethers.core.types.transaction.TransactionRecovered -import io.ethers.core.types.transaction.TxBlob import io.ethers.core.types.transaction.TxType import java.math.BigInteger @@ -47,10 +46,7 @@ data class RPCTransaction( override val blobVersionedHashes: List?, override val blobFeeCap: BigInteger?, val otherFields: Map = emptyMap(), -) : TransactionRecovered { - override val blobGas: Long - get() = blobVersionedHashes?.size?.toLong()?.times(TxBlob.GAS_PER_BLOB) ?: 0 -} +) : TransactionRecovered private class RPCTransactionDeserializer : JsonDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext): RPCTransaction { @@ -131,7 +127,7 @@ private class RPCTransactionDeserializer : JsonDeserializer() { data, accessList, chainId, - TxType.entries[type.toInt()], + TxType.fromType(type.toInt()), v, r, s, diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/TransactionReceipt.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/TransactionReceipt.kt index c7774f80..737e94e0 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/TransactionReceipt.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/TransactionReceipt.kt @@ -12,9 +12,11 @@ import io.ethers.core.readBloom import io.ethers.core.readBytes import io.ethers.core.readHash import io.ethers.core.readHexBigInteger +import io.ethers.core.readHexInt import io.ethers.core.readHexLong import io.ethers.core.readListOf import io.ethers.core.readOrNull +import io.ethers.core.types.transaction.TxType import java.math.BigInteger /** @@ -33,7 +35,7 @@ data class TransactionReceipt( val contractAddress: Address?, val logs: List, val logsBloom: Bloom, - val type: Long, + val type: TxType, val effectiveGasPrice: BigInteger, val status: Long, val root: Bytes?, @@ -60,7 +62,7 @@ private class TxReceiptDeserializer : JsonDeserializer() { var contractAddress: Address? = null var logs = emptyList() lateinit var logsBloom: Bloom - var type: Long = -1L + var type: Int = -1 lateinit var effectiveGasPrice: BigInteger var status: Long = -1L var root: Bytes? = null @@ -79,7 +81,7 @@ private class TxReceiptDeserializer : JsonDeserializer() { "contractAddress" -> contractAddress = p.readOrNull { readAddress() } "logs" -> logs = p.readListOf(Log::class.java) "logsBloom" -> logsBloom = p.readBloom() - "type" -> type = p.readHexLong() + "type" -> type = p.readHexInt() "effectiveGasPrice" -> effectiveGasPrice = p.readHexBigInteger() "status" -> status = p.readHexLong() "root" -> root = p.readBytes() @@ -104,7 +106,7 @@ private class TxReceiptDeserializer : JsonDeserializer() { contractAddress, logs, logsBloom, - type, + TxType.fromType(type), effectiveGasPrice, status, root, diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/Transaction.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/Transaction.kt index b4603874..19b7f166 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/Transaction.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/Transaction.kt @@ -32,29 +32,57 @@ interface Transaction { val type: TxType val blobFeeCap: BigInteger? val blobVersionedHashes: List? + val blobGas: Long + get() = blobVersionedHashes?.size?.toLong()?.times(TxBlob.GAS_PER_BLOB) ?: 0 } /** - * Supported transaction types. - */ -enum class TxType(val value: Int) { - LEGACY(0x0), - ACCESS_LIST(0x1), - DYNAMIC_FEE(0x2), - BLOB(0x3), - ; + * [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) Transaction Type. + * + * If type is not officially supported by this library - meaning it cannot construct, sign, and send it -, it will be + * represented as [TxType.Unsupported]. Unsupported tx types can still be received from the network. + * */ +sealed class TxType(val type: Int) { + /** + * @return true if this transaction type is supported by this library, false otherwise. + * */ + val isSupported: Boolean + get() = this !is Unsupported + + data object Legacy : TxType(0x0) + data object AccessList : TxType(0x1) + data object DynamicFee : TxType(0x2) + data object Blob : TxType(0x3) + + /** + * A transaction type that is not supported by this library, but can still be received from the network. + * */ + class Unsupported(type: Int) : TxType(type) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + return type == (other as Unsupported).type + } + + override fun hashCode(): Int { + return type + } + + override fun toString(): String { + return "Unknown(type=$type)" + } + } companion object { - // optimization to avoid allocating an iterator - fun findOrNull(value: Int): TxType? { - for (i in entries.indices) { - val entry = entries[i] - if (entry.value == value) { - return entry - } + fun fromType(type: Int): TxType { + return when (type) { + Legacy.type -> Legacy + AccessList.type -> AccessList + DynamicFee.type -> DynamicFee + Blob.type -> Blob + else -> Unsupported(type) } - return null } } } diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionSigned.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionSigned.kt index 4921690f..f505e103 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionSigned.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionSigned.kt @@ -106,8 +106,8 @@ class TransactionSigned @JvmOverloads constructor( private fun rlpEncode(rlp: RlpEncoder, hashEncoding: Boolean) { // non-legacy txs are enveloped based on eip2718 - if (tx.type != TxType.LEGACY) { - rlp.appendRaw(tx.type.value.toByte()) + if (tx.type != TxType.Legacy) { + rlp.appendRaw(tx.type.type.toByte()) } rlp.encodeList { @@ -119,7 +119,7 @@ class TransactionSigned @JvmOverloads constructor( // signature values. // // See: https://eips.ethereum.org/EIPS/eip-4844#networking - if (!hashEncoding && tx.type == TxType.BLOB && (tx as TxBlob).sidecar != null) { + if (!hashEncoding && tx.type == TxType.Blob && (tx as TxBlob).sidecar != null) { rlp.encodeList { tx.rlpEncodeFields(this) signature.rlpEncode(this) @@ -167,9 +167,9 @@ class TransactionSigned @JvmOverloads constructor( } } - return when (TxType.findOrNull(type)) { - TxType.LEGACY -> throw IllegalStateException("Should not happen") - TxType.ACCESS_LIST -> { + return when (TxType.fromType(type)) { + TxType.Legacy -> throw IllegalStateException("Should not happen") + TxType.AccessList -> { rlp.readByte() rlp.decodeList { @@ -179,7 +179,7 @@ class TransactionSigned @JvmOverloads constructor( } } - TxType.DYNAMIC_FEE -> { + TxType.DynamicFee -> { rlp.readByte() rlp.decodeList { @@ -189,7 +189,7 @@ class TransactionSigned @JvmOverloads constructor( } } - TxType.BLOB -> { + TxType.Blob -> { rlp.readByte() rlp.decodeList { @@ -216,7 +216,7 @@ class TransactionSigned @JvmOverloads constructor( } } - null -> null + is TxType.Unsupported -> null } } } diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionUnsigned.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionUnsigned.kt index 462ce8eb..e0f8b1e8 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionUnsigned.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TransactionUnsigned.kt @@ -28,8 +28,8 @@ sealed interface TransactionUnsigned : Transaction { */ fun rlpEncode(encoder: RlpEncoder, forSignatureHash: Boolean = false) { // non-legacy txs are enveloped based on eip2718 - if (type != TxType.LEGACY) { - encoder.appendRaw(type.value.toByte()) + if (type != TxType.Legacy) { + encoder.appendRaw(type.type.toByte()) } encoder.encodeList { @@ -43,7 +43,7 @@ sealed interface TransactionUnsigned : Transaction { return@encodeList } - if (type == TxType.LEGACY && ChainId.isValid(chainId)) { + if (type == TxType.Legacy && ChainId.isValid(chainId)) { // EIP-155 support for LegacyTx, applies only if we have a valid chainId // see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md encoder.encode(chainId) @@ -75,25 +75,25 @@ sealed interface TransactionUnsigned : Transaction { return rlp.decodeList { TxLegacy.rlpDecode(rlp, chainId).also { dropEmptyRSV() } } } - return when (TxType.findOrNull(type)) { - TxType.LEGACY -> throw IllegalStateException("Should not happen") + return when (TxType.fromType(type)) { + TxType.Legacy -> throw IllegalStateException("Should not happen") - TxType.ACCESS_LIST -> { + TxType.AccessList -> { rlp.readByte() rlp.decodeList { TxAccessList.rlpDecode(rlp).also { dropEmptyRSV() } } } - TxType.DYNAMIC_FEE -> { + TxType.DynamicFee -> { rlp.readByte() rlp.decodeList { TxDynamicFee.rlpDecode(rlp).also { dropEmptyRSV() } } } - TxType.BLOB -> { + TxType.Blob -> { rlp.readByte() rlp.decodeList { TxBlob.rlpDecode(rlp).also { dropEmptyRSV() } } } - null -> null + is TxType.Unsupported -> null } } diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxAccessList.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxAccessList.kt index 081e1e50..c401664d 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxAccessList.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxAccessList.kt @@ -37,7 +37,7 @@ class TxAccessList( get() = gasPrice override val type: TxType - get() = TxType.ACCESS_LIST + get() = TxType.AccessList override val blobFeeCap: BigInteger? get() = null @@ -45,9 +45,6 @@ class TxAccessList( override val blobVersionedHashes: List? get() = null - override val blobGas: Long - get() = 0 - override fun rlpEncodeFields(rlp: RlpEncoder) { rlp.encode(chainId) rlp.encode(nonce) diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxBlob.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxBlob.kt index 00208371..d96a2fd6 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxBlob.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxBlob.kt @@ -73,10 +73,7 @@ class TxBlob( get() = gasFeeCap override val type: TxType - get() = TxType.BLOB - - override val blobGas: Long - get() = GAS_PER_BLOB * blobVersionedHashes.size.toLong() + get() = TxType.Blob override fun rlpEncodeFields(rlp: RlpEncoder) { rlp.encode(chainId) diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxDynamicFee.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxDynamicFee.kt index dbefe5dc..4f9db902 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxDynamicFee.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxDynamicFee.kt @@ -38,7 +38,7 @@ class TxDynamicFee( get() = gasFeeCap override val type: TxType - get() = TxType.DYNAMIC_FEE + get() = TxType.DynamicFee override val blobFeeCap: BigInteger? get() = null @@ -46,9 +46,6 @@ class TxDynamicFee( override val blobVersionedHashes: List? get() = null - override val blobGas: Long - get() = 0 - override fun rlpEncodeFields(rlp: RlpEncoder) { rlp.encode(chainId) rlp.encode(nonce) diff --git a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxLegacy.kt b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxLegacy.kt index 8b965594..4831bd23 100644 --- a/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxLegacy.kt +++ b/ethers-core/src/main/kotlin/io/ethers/core/types/transaction/TxLegacy.kt @@ -28,7 +28,7 @@ data class TxLegacy( get() = null override val type: TxType - get() = TxType.LEGACY + get() = TxType.Legacy override val blobFeeCap: BigInteger? get() = null @@ -36,9 +36,6 @@ data class TxLegacy( override val blobVersionedHashes: List? get() = null - override val blobGas: Long - get() = 0 - override fun rlpEncodeFields(rlp: RlpEncoder) { rlp.encode(nonce) rlp.encode(gasPrice) diff --git a/ethers-core/src/test/kotlin/io/ethers/core/types/BlockTest.kt b/ethers-core/src/test/kotlin/io/ethers/core/types/BlockTest.kt index 5b0fc75f..3b76e75c 100644 --- a/ethers-core/src/test/kotlin/io/ethers/core/types/BlockTest.kt +++ b/ethers-core/src/test/kotlin/io/ethers/core/types/BlockTest.kt @@ -242,7 +242,7 @@ class BlockTest : FunSpec({ to = Address("0xb0bababe78a9be0810fadf99dd2ed31ed12568be"), transactionIndex = 1L, value = BigInteger("10000000000000000"), - type = TxType.DYNAMIC_FEE, + type = TxType.DynamicFee, accessList = listOf( AccessList.Item( Address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), diff --git a/ethers-core/src/test/kotlin/io/ethers/core/types/RPCTransactionTest.kt b/ethers-core/src/test/kotlin/io/ethers/core/types/RPCTransactionTest.kt index b78b85c3..1c8f2da8 100644 --- a/ethers-core/src/test/kotlin/io/ethers/core/types/RPCTransactionTest.kt +++ b/ethers-core/src/test/kotlin/io/ethers/core/types/RPCTransactionTest.kt @@ -64,7 +64,7 @@ class RPCTransactionTest : FunSpec({ to = Address("0xb0bababe78a9be0810fadf99dd2ed31ed12568be"), transactionIndex = 1L, value = BigInteger("10000000000000000"), - type = TxType.DYNAMIC_FEE, + type = TxType.DynamicFee, accessList = listOf( AccessList.Item( Address("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), diff --git a/ethers-core/src/test/kotlin/io/ethers/core/types/TransactionReceiptTest.kt b/ethers-core/src/test/kotlin/io/ethers/core/types/TransactionReceiptTest.kt index 697e2658..2803bad5 100644 --- a/ethers-core/src/test/kotlin/io/ethers/core/types/TransactionReceiptTest.kt +++ b/ethers-core/src/test/kotlin/io/ethers/core/types/TransactionReceiptTest.kt @@ -79,7 +79,7 @@ class TransactionReceiptTest : FunSpec({ to = Address("0x881d40237659c251811cec9c364ef91dc08d300c"), transactionHash = Hash("0xce15f8ce74845b0d254fcbfda722ba89976ca6e09936d6761a648a6492b82e9b"), transactionIndex = 1, - type = TxType.DYNAMIC_FEE.value.toLong(), + type = TxType.DynamicFee, root = Bytes("0x5f5755290000000000000000000000000000000000000000000000000000000000000080"), otherFields = mapOf( "test_tx" to Jackson.MAPPER.readTree("""{"k1_tx":"v1_tx","k2_tx":"v2_tx"}"""), diff --git a/ethers-core/src/test/kotlin/io/ethers/core/types/TxpoolTest.kt b/ethers-core/src/test/kotlin/io/ethers/core/types/TxpoolTest.kt index 39c1929a..79159038 100644 --- a/ethers-core/src/test/kotlin/io/ethers/core/types/TxpoolTest.kt +++ b/ethers-core/src/test/kotlin/io/ethers/core/types/TxpoolTest.kt @@ -75,7 +75,7 @@ class TxpoolTest : FunSpec({ to = Address("0xf3474e17e5f7069a2a3a85da77bcedff34183efd"), transactionIndex = -1L, value = BigInteger("635790000000000"), - type = TxType.LEGACY, + type = TxType.Legacy, chainId = 1L, v = 38L, r = BigInteger("23149443838906753736590725708700793907547588851307035983763567886914160398361"), @@ -103,7 +103,7 @@ class TxpoolTest : FunSpec({ to = Address("0x111111111117dc0aa78b770fa6a738034120c302"), transactionIndex = -1L, value = BigInteger.ZERO, - type = TxType.LEGACY, + type = TxType.Legacy, chainId = 1L, v = 37L, r = BigInteger("65931866719980242200380868427802295846741631999272549801893218085394959832269"), @@ -197,7 +197,7 @@ class TxpoolTest : FunSpec({ to = Address("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), transactionIndex = -1, value = BigInteger("23000000000000000"), - type = TxType.LEGACY, + type = TxType.Legacy, chainId = 1L, accessList = null, gasFeeCap = BigInteger("53739672778"), @@ -223,7 +223,7 @@ class TxpoolTest : FunSpec({ to = Address("0xc7757805b983ee1b6272c1840c18e66837de858e"), transactionIndex = -1, value = BigInteger.ZERO, - type = TxType.LEGACY, + type = TxType.Legacy, chainId = 1L, accessList = null, gasFeeCap = BigInteger("5500000000"), diff --git a/ethers-providers/src/test/kotlin/io/ethers/providers/types/PendingTransactionTest.kt b/ethers-providers/src/test/kotlin/io/ethers/providers/types/PendingTransactionTest.kt index 58e0a873..f0f83bcb 100644 --- a/ethers-providers/src/test/kotlin/io/ethers/providers/types/PendingTransactionTest.kt +++ b/ethers-providers/src/test/kotlin/io/ethers/providers/types/PendingTransactionTest.kt @@ -218,7 +218,7 @@ private val TX_RECEIPT = TransactionReceipt( to = Address("0x881d40237659c251811cec9c364ef91dc08d300c"), transactionHash = Hash("0xce15f8ce74845b0d254fcbfda722ba89976ca6e09936d6761a648a6492b82e9b"), transactionIndex = 1, - type = TxType.DYNAMIC_FEE.value.toLong(), + type = TxType.DynamicFee, root = Bytes("0x5f5755290000000000000000000000000000000000000000000000000000000000000080"), otherFields = mapOf( "test_tx" to Jackson.MAPPER.readTree("""{"k1_tx":"v1_tx","k2_tx":"v2_tx"}"""), diff --git a/ethers-signers/src/main/kotlin/io/ethers/signers/Signer.kt b/ethers-signers/src/main/kotlin/io/ethers/signers/Signer.kt index 403dca26..5e135697 100644 --- a/ethers-signers/src/main/kotlin/io/ethers/signers/Signer.kt +++ b/ethers-signers/src/main/kotlin/io/ethers/signers/Signer.kt @@ -26,7 +26,7 @@ interface Signer { fun signTransaction(tx: TransactionUnsigned): TransactionSigned { val sig = signHash(tx.signatureHash()) - if (tx.type == TxType.LEGACY) { + if (tx.type == TxType.Legacy) { // applies EIP-155 replay protection if we have a valid chainId for legacy tx // see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md if (ChainId.isValid(tx.chainId)) {