diff --git a/build.gradle b/build.gradle index 2629281e..e180afbb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { // App version - versionName = '2.3.0' + versionName = '2.4.0' versionCode = 1 // SDK and tools diff --git a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/Extrinsic.kt b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/Extrinsic.kt index 55581e64..13a780f4 100644 --- a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/Extrinsic.kt +++ b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/Extrinsic.kt @@ -5,35 +5,24 @@ package io.novasama.substrate_sdk_android.runtime.definitions.types.generics import io.emeraldpay.polkaj.scale.ScaleCodecReader import io.emeraldpay.polkaj.scale.ScaleCodecWriter import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot -import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType import io.novasama.substrate_sdk_android.runtime.definitions.types.Type import io.novasama.substrate_sdk_android.runtime.definitions.types.bytes import io.novasama.substrate_sdk_android.runtime.definitions.types.errors.EncodeDecodeException import io.novasama.substrate_sdk_android.runtime.definitions.types.toByteArray +import io.novasama.substrate_sdk_android.runtime.definitions.types.useScaleWriter import io.novasama.substrate_sdk_android.scale.dataType.byte import io.novasama.substrate_sdk_android.scale.dataType.compactInt +import io.novasama.substrate_sdk_android.scale.dataType.toByteArray +import io.novasama.substrate_sdk_android.scale.utils.directWrite private val SIGNED_MASK = 0b1000_0000.toUByte() private const val TYPE_ADDRESS = "Address" private const val TYPE_SIGNATURE = "ExtrinsicSignature" -@OptIn(ExperimentalUnsignedTypes::class) -object Extrinsic : RuntimeType("ExtrinsicsDecoder") { +object Extrinsic : Type("ExtrinsicsDecoder") { - class EncodingInstance( - val signature: Signature?, - val callRepresentation: CallRepresentation - ) { - sealed class CallRepresentation { - - class Instance(val call: GenericCall.Instance) : CallRepresentation() - - class Bytes(val bytes: ByteArray) : CallRepresentation() - } - } - - class DecodedInstance( + class Instance( val signature: Signature?, val call: GenericCall.Instance ) @@ -56,7 +45,7 @@ object Extrinsic : RuntimeType - GenericCall.toByteArray(runtime, value.callRepresentation.call) - - is EncodingInstance.CallRepresentation.Bytes -> value.callRepresentation.bytes - } + encode(scaleCodecWriter, runtime, value, encodeLength = true) + } - encode(scaleCodecWriter, runtime, value.signature, callBytes) + fun encodeWithoutLength( + scaleCodecWriter: ScaleCodecWriter, + runtime: RuntimeSnapshot, + value: Instance + ) { + encode(scaleCodecWriter, runtime, value, encodeLength = false) } private fun encode( scaleCodecWriter: ScaleCodecWriter, runtime: RuntimeSnapshot, - signature: Signature?, - callBytes: ByteArray + value: Instance, + encodeLength: Boolean, ) { + val signature = value.signature + val callBytes = GenericCall.toByteArray(runtime, value.call) + val isSigned = signature != null val extrinsicVersion = runtime.metadata.extrinsic.version.toInt().toUByte() @@ -116,11 +109,15 @@ object Extrinsic : RuntimeType { value = addressId ) } - -fun Extrinsic.EncodingInstance( - signature: Extrinsic.Signature?, - call: GenericCall.Instance -): Extrinsic.EncodingInstance { - return Extrinsic.EncodingInstance( - signature, - Extrinsic.EncodingInstance.CallRepresentation.Instance(call) - ) -} - -fun Extrinsic.EncodingInstance( - signature: Extrinsic.Signature?, - callBytes: ByteArray -): Extrinsic.EncodingInstance { - return Extrinsic.EncodingInstance( - signature, - Extrinsic.EncodingInstance.CallRepresentation.Bytes(callBytes) - ) -} diff --git a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilder.kt b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilder.kt index a5de4618..38ac6f67 100644 --- a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilder.kt +++ b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilder.kt @@ -7,14 +7,11 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.DefaultSignedExtensions import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Era import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic -import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.ExtrinsicPayloadExtrasInstance import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.new import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.SignatureInstanceConstructor -import io.novasama.substrate_sdk_android.runtime.definitions.types.toHex -import io.novasama.substrate_sdk_android.runtime.definitions.types.toHexUntyped import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SendableExtrinsic import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignedExtrinsic import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.Signer @@ -114,14 +111,7 @@ class ExtrinsicBuilder( batchMode: BatchMode = BatchMode.BATCH ): SendableExtrinsic { val call = maybeWrapInBatch(batchMode) - return buildSendableExtrinsic(CallRepresentation.Instance(call)) - } - - suspend fun buildExtrinsic( - rawCallBytes: ByteArray - ): SendableExtrinsic { - requireNotMixingBytesAndInstanceCalls() - return buildSendableExtrinsic(CallRepresentation.Bytes(rawCallBytes)) + return buildSendableExtrinsic(call) } private fun maybeWrapInBatch(batchMode: BatchMode): GenericCall.Instance { @@ -134,13 +124,11 @@ class ExtrinsicBuilder( } } - private suspend fun buildSendableExtrinsic( - callRepresentation: CallRepresentation - ): SendableExtrinsic { + private suspend fun buildSendableExtrinsic(call: GenericCall.Instance): SendableExtrinsic { val signerPayload = SignerPayloadExtrinsic( runtime = runtime, accountId = accountId, - call = callRepresentation, + call = call, signedExtras = SignerPayloadExtrinsic.SignedExtras( includedInExtrinsic = buildIncludedInExtrinsic(), includedInSignature = buildIncludedInSignature() @@ -150,8 +138,25 @@ class ExtrinsicBuilder( ) val signedExtrinsic = signer.signExtrinsic(signerPayload) + val instance = buildEncodingExtrinsic(signedExtrinsic) + + return SendableExtrinsic(runtime, instance) + } - return RealSendableExtrinsic(signedExtrinsic) + private fun buildEncodingExtrinsic(signedExtrinsic: SignedExtrinsic): Extrinsic.Instance { + val address = buildEncodableAddressInstance(signedExtrinsic.payload.accountId) + val multiSignature = signatureConstructor.constructInstance( + runtime.typeRegistry, + signedExtrinsic.signatureWrapper + ) + return Extrinsic.Instance( + signature = Extrinsic.Signature.new( + accountIdentifier = address, + signature = multiSignature, + signedExtras = signedExtrinsic.payload.signedExtras.includedInExtrinsic + ), + call = signedExtrinsic.payload.call + ) } private fun buildIncludedInSignature(): Map { @@ -223,41 +228,4 @@ class ExtrinsicBuilder( "Cannot mix instance and raw bytes calls" } } - - private inner class RealSendableExtrinsic( - private val signedExtrinsic: SignedExtrinsic - ) : SendableExtrinsic { - - private val multiSignature = signatureConstructor.constructInstance( - runtime.typeRegistry, - signedExtrinsic.signatureWrapper - ) - - override val extrinsicHex by lazy { - createExtrinsicHex() - } - - override val signatureHex by lazy { - createSignatureHex() - } - - private fun createExtrinsicHex(): String { - val address = buildEncodableAddressInstance(signedExtrinsic.payload.accountId) - val extrinsic = Extrinsic.EncodingInstance( - signature = Extrinsic.Signature.new( - accountIdentifier = address, - signature = multiSignature, - signedExtras = signedExtrinsic.payload.signedExtras.includedInExtrinsic - ), - callRepresentation = signedExtrinsic.payload.call - ) - - return Extrinsic.toHex(runtime, extrinsic) - } - - private fun createSignatureHex(): String { - val signatureType = Extrinsic.signatureType(runtime) - return signatureType.toHexUntyped(runtime, multiSignature) - } - } } diff --git a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SendableExtrinsic.kt b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SendableExtrinsic.kt index 4fdc924b..54d6f47d 100644 --- a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SendableExtrinsic.kt +++ b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SendableExtrinsic.kt @@ -1,8 +1,57 @@ package io.novasama.substrate_sdk_android.runtime.extrinsic.signer +import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot +import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic +import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.toBytesWithoutLength +import io.novasama.substrate_sdk_android.runtime.definitions.types.toHex +import io.novasama.substrate_sdk_android.runtime.definitions.types.toHexUntyped + interface SendableExtrinsic { + val extrinsic: Extrinsic.Instance + val signatureHex: String val extrinsicHex: String + + val bytesWithoutLength: ByteArray +} + +fun SendableExtrinsic( + runtime: RuntimeSnapshot, + extrinsic: Extrinsic.Instance +): SendableExtrinsic { + return RealSendableExtrinsic(runtime, extrinsic) +} + +private class RealSendableExtrinsic( + private val runtime: RuntimeSnapshot, + override val extrinsic: Extrinsic.Instance +) : SendableExtrinsic { + + override val extrinsicHex by lazy { + createExtrinsicHex() + } + + override val bytesWithoutLength: ByteArray by lazy { + Extrinsic.toBytesWithoutLength(runtime, extrinsic) + } + + override val signatureHex by lazy { + createSignatureHex() + } + + private fun createExtrinsicHex(): String { + return Extrinsic.toHex(runtime, extrinsic) + } + + private fun createSignatureHex(): String { + requireNotNull(extrinsic.signature) { + "Extrinsic is unsigned" + } + + val signatureType = Extrinsic.signatureType(runtime) + + return signatureType.toHexUntyped(runtime, extrinsic.signature.signature) + } } diff --git a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/Signer.kt b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/Signer.kt index 66db12e1..9c1f1fb0 100644 --- a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/Signer.kt +++ b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/Signer.kt @@ -4,7 +4,7 @@ import io.novasama.substrate_sdk_android.encrypt.SignatureWrapper import io.novasama.substrate_sdk_android.extensions.fromHex import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot -import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation +import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall import io.novasama.substrate_sdk_android.runtime.extrinsic.Nonce class SignedExtrinsic( @@ -28,7 +28,7 @@ class SignerPayloadRaw( data class SignerPayloadExtrinsic( val runtime: RuntimeSnapshot, val accountId: AccountId, - val call: CallRepresentation, + val call: GenericCall.Instance, val signedExtras: SignedExtras, val nonce: Nonce, ) { diff --git a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SignerExt.kt b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SignerExt.kt index f716ccf4..0a88df16 100644 --- a/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SignerExt.kt +++ b/substrate-sdk-android/src/main/java/io/novasama/substrate_sdk_android/runtime/extrinsic/signer/SignerExt.kt @@ -5,10 +5,8 @@ import io.novasama.substrate_sdk_android.hash.Hasher.blake2b256 import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.DefaultSignedExtensions import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.ExtrasIncludedInExtrinsic import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.ExtrasIncludedInSignature -import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall import io.novasama.substrate_sdk_android.runtime.definitions.types.useScaleWriter -import io.novasama.substrate_sdk_android.scale.utils.directWrite private const val PAYLOAD_HASH_THRESHOLD = 256 @@ -18,13 +16,7 @@ fun bytesOf(vararg writers: (ScaleCodecWriter) -> Unit) = useScaleWriter { } fun SignerPayloadExtrinsic.encodeCallDataTo(writer: ScaleCodecWriter) { - when (call) { - is Extrinsic.EncodingInstance.CallRepresentation.Bytes -> - writer.directWrite(call.bytes) - - is Extrinsic.EncodingInstance.CallRepresentation.Instance -> - GenericCall.encode(writer, runtime, call.call) - } + GenericCall.encode(writer, runtime, call) } fun SignerPayloadExtrinsic.encodedCallData() = bytesOf(::encodeCallDataTo) diff --git a/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/ExtrinsicTest.kt b/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/ExtrinsicTest.kt index 29a38da3..76806726 100644 --- a/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/ExtrinsicTest.kt +++ b/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/definitions/types/generics/ExtrinsicTest.kt @@ -6,7 +6,6 @@ import io.novasama.substrate_sdk_android.extensions.toHexString import io.novasama.substrate_sdk_android.runtime.RealRuntimeProvider import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum import io.novasama.substrate_sdk_android.runtime.definitions.types.fromHex -import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Extrinsic.EncodingInstance.* import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.SignatureInstanceConstructor import io.novasama.substrate_sdk_android.runtime.definitions.types.toHex @@ -73,7 +72,7 @@ class ExtrinsicTest { value = SignatureWrapper.Sr25519(signatureInHex.fromHex()) ) - val extrinsic = Extrinsic.EncodingInstance( + val extrinsic = Extrinsic.Instance( signature = Extrinsic.Signature.new( accountIdentifier = AddressInstanceConstructor.constructInstance( typeRegistry = runtime.typeRegistry, @@ -82,7 +81,7 @@ class ExtrinsicTest { signature = signature, signedExtras = signedExtras ), - callRepresentation = CallRepresentation.Instance(call) + call = call ) val encoded = Extrinsic.toHex(runtime, extrinsic) diff --git a/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilderTest.kt b/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilderTest.kt index b832cf8b..8d9fcc1c 100644 --- a/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilderTest.kt +++ b/substrate-sdk-android/src/test/java/io/novasama/substrate_sdk_android/runtime/extrinsic/ExtrinsicBuilderTest.kt @@ -29,8 +29,7 @@ private val KEYPAIR = BaseKeypair( private const val SINGLE_TRANSFER_EXTRINSIC = "0x41028400fdc41550fb5186d71cae699c31731b3e1baa10680c7bd6b3831a6d222cf4d16800080bfe8bc67f44b498239887dc5679523cfcb1d20fd9ec9d6bae0a385cca118d2cb7ef9f4674d52a810feb32932d7c6fe3e05ce9e06cd72cf499c8692206410ab5038800040000340a806419d5e278172e45cb0e50da1b031795366c99ddfe0a680bd53b142c630700e40b5402" -private val TRANSFER_CALL_BYTES = - "0x040000340a806419d5e278172e45cb0e50da1b031795366c99ddfe0a680bd53b142c630700e40b5402".fromHex() + private const val EXTRINSIC_SIGNATURE = "0x00080bfe8bc67f44b498239887dc5679523cfcb1d20fd9ec9d6bae0a385cca118d2cb7ef9f4674d52a810feb32932d7c6fe3e05ce9e06cd72cf499c8692206410a" @@ -48,14 +47,6 @@ class ExtrinsicBuilderTest { val runtime = RealRuntimeProvider.buildRuntime("westend") - @Test - fun `should build extrinsic from raw call bytes`() = runBlockingTest { - val extrinsic = createExtrinsicBuilder() - .buildExtrinsic(TRANSFER_CALL_BYTES) - - assertEquals(SINGLE_TRANSFER_EXTRINSIC, extrinsic.extrinsicHex) - } - @Test fun `should build single transfer extrinsic`() = runBlockingTest { val extrinsic = createExtrinsicBuilder() @@ -74,14 +65,6 @@ class ExtrinsicBuilderTest { assertEquals(EXTRINSIC_SIGNATURE, extrinsic.signatureHex) } - @Test - fun `should build extrinsic signature from raw call bytes`() = runBlockingTest { - val extrinsic = createExtrinsicBuilder() - .buildExtrinsic(TRANSFER_CALL_BYTES) - - assertEquals(EXTRINSIC_SIGNATURE, extrinsic.signatureHex) - } - @Test fun `should replace call`() = runBlockingTest { val wrongAMount = "123".toBigInteger()