diff --git a/build.gradle b/build.gradle index bd0965d9..93b05c53 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext { // App version - versionName = '1.9.0' + versionName = '1.9.1' versionCode = 1 // SDK and tools diff --git a/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilder.kt b/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilder.kt index 73427d17..58510658 100644 --- a/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilder.kt +++ b/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilder.kt @@ -20,6 +20,7 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.toHexUntyped import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.metadata.SignedExtensionId +import jp.co.soramitsu.fearless_utils.runtime.metadata.SignedExtensionValue import jp.co.soramitsu.fearless_utils.runtime.metadata.call import jp.co.soramitsu.fearless_utils.runtime.metadata.findSignedExtension import jp.co.soramitsu.fearless_utils.runtime.metadata.module @@ -38,13 +39,16 @@ class ExtrinsicBuilder( private val blockHash: ByteArray = genesisHash, private val era: Era = Era.Immortal, private val tip: BigInteger = DEFAULT_TIP, - private val customSignedExtensions: Map = emptyMap(), + customSignedExtensions: Map = emptyMap(), private val addressInstanceConstructor: RuntimeType.InstanceConstructor = AddressInstanceConstructor, private val signatureConstructor: RuntimeType.InstanceConstructor = SignatureInstanceConstructor ) { private val calls = mutableListOf() + private val _customSignedExtensions = mutableMapOf() + .apply { putAll(customSignedExtensions) } + fun call( moduleIndex: Int, callIndex: Int, @@ -77,7 +81,20 @@ class ExtrinsicBuilder( return this } - fun reset(): ExtrinsicBuilder { + fun signedExtension( + id: SignedExtensionId, + value: SignedExtensionValue + ) { + _customSignedExtensions[id] = value + } + + @Deprecated( + message = "Use restCalls() for better readability", + replaceWith = ReplaceWith(expression = "resetCalls()") + ) + fun reset(): ExtrinsicBuilder = resetCalls() + + fun resetCalls(): ExtrinsicBuilder { calls.clear() return this @@ -153,26 +170,36 @@ class ExtrinsicBuilder( private suspend fun buildSignatureObject(callRepresentation: CallRepresentation): Any? { val signedExtrasInstance = buildSignedExtras() - - val additionalExtrasInstance = mapOf( - DefaultSignedExtensions.CHECK_MORTALITY to blockHash, - DefaultSignedExtensions.CHECK_GENESIS to genesisHash, - DefaultSignedExtensions.CHECK_SPEC_VERSION to runtimeVersion.specVersion.toBigInteger(), - DefaultSignedExtensions.CHECK_TX_VERSION to runtimeVersion.transactionVersion.toBigInteger() - ) + val additionalSignedInstance = buildAdditionalSigned() val signerPayload = SignerPayloadExtrinsic( runtime = runtime, accountId = accountId, call = callRepresentation, signedExtras = signedExtrasInstance, - additionalSignedExtras = additionalExtrasInstance, + additionalSignedExtras = additionalSignedInstance, ) val signatureWrapper = signer.signExtrinsic(signerPayload) return signatureConstructor.constructInstance(runtime.typeRegistry, signatureWrapper) } + private fun buildAdditionalSigned(): Map { + val default = mapOf( + DefaultSignedExtensions.CHECK_MORTALITY to blockHash, + DefaultSignedExtensions.CHECK_GENESIS to genesisHash, + DefaultSignedExtensions.CHECK_SPEC_VERSION to runtimeVersion.specVersion.toBigInteger(), + DefaultSignedExtensions.CHECK_TX_VERSION to + runtimeVersion.transactionVersion.toBigInteger() + ) + + val custom = _customSignedExtensions.mapValues { (_, extensionValues) -> + extensionValues.additionalSigned + } + + return default + custom + } + private fun wrapInBatch(useBatchAll: Boolean): GenericCall.Instance { val batchModule = runtime.metadata.module("Utility") val batchFunctionName = if (useBatchAll) "batch_all" else "batch" @@ -198,7 +225,11 @@ class ExtrinsicBuilder( DefaultSignedExtensions.CHECK_NONCE to encodeNonce(nonce) ) - return default + customSignedExtensions + val custom = _customSignedExtensions.mapValues { (_, extensionValues) -> + extensionValues.signedExtra + } + + return default + custom } private fun encodeNonce(nonce: BigInteger): Any { diff --git a/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderExt.kt b/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderExt.kt new file mode 100644 index 00000000..d6cf514f --- /dev/null +++ b/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderExt.kt @@ -0,0 +1,12 @@ +package jp.co.soramitsu.fearless_utils.runtime.extrinsic + +import jp.co.soramitsu.fearless_utils.runtime.metadata.SignedExtensionId +import jp.co.soramitsu.fearless_utils.runtime.metadata.SignedExtensionValue + +fun ExtrinsicBuilder.signedExtra(id: SignedExtensionId, value: Any?) { + signedExtension(id, SignedExtensionValue(signedExtra = value)) +} + +fun ExtrinsicBuilder.additionalSigned(id: SignedExtensionId, value: Any?) { + signedExtension(id, SignedExtensionValue(additionalSigned = value)) +} diff --git a/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/metadata/RuntimeMetadata.kt b/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/metadata/RuntimeMetadata.kt index 490989a4..11fc9539 100644 --- a/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/metadata/RuntimeMetadata.kt +++ b/fearless-utils/src/main/java/jp/co/soramitsu/fearless_utils/runtime/metadata/RuntimeMetadata.kt @@ -24,6 +24,11 @@ class ExtrinsicMetadata( typealias SignedExtensionId = String +class SignedExtensionValue( + val signedExtra: Any? = null, + val additionalSigned: Any? = null +) + class SignedExtensionMetadata( val id: SignedExtensionId, val type: RuntimeType<*, *>?, @@ -32,10 +37,20 @@ class SignedExtensionMetadata( companion object { + /** + * SignedExtras is signature params that are both signed + * and put separately in payload for verification + * Examples: tip, mortality + */ fun onlySigned(id: String, type: RuntimeType<*, *>): SignedExtensionMetadata { return SignedExtensionMetadata(id, type, Null) } + /** + * AdditionalSigned is signature params that are signed + * and that are verified by runtime based on-chain state + * Examples: genesis hash, runtime version + */ fun onlyAdditional(id: String, additionalSigned: RuntimeType<*, *>): SignedExtensionMetadata { return SignedExtensionMetadata(id, Null, additionalSigned) } diff --git a/fearless-utils/src/test/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderTest.kt b/fearless-utils/src/test/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderTest.kt index acdd332d..6351e438 100644 --- a/fearless-utils/src/test/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderTest.kt +++ b/fearless-utils/src/test/java/jp/co/soramitsu/fearless_utils/runtime/extrinsic/ExtrinsicBuilderTest.kt @@ -10,8 +10,8 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHex import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Era import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.multiAddressFromId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner +import jp.co.soramitsu.fearless_utils.runtime.metadata.SignedExtensionValue import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.publicKeyToSubstrateAccountId import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId import jp.co.soramitsu.fearless_utils.wsrpc.request.runtime.chain.RuntimeVersion @@ -47,7 +47,7 @@ class ExtrinsicBuilderTest { val runtime = RealRuntimeProvider.buildRuntime("westend") @Test - fun `should build extrinsic from raw call bytes`() = runBlockingTest{ + fun `should build extrinsic from raw call bytes`() = runBlockingTest { val extrinsic = createExtrinsicBuilder() .build(TRANSFER_CALL_BYTES) @@ -118,7 +118,7 @@ class ExtrinsicBuilderTest { } @Test - fun `should build batch_all extrinsic`() = runBlockingTest{ + fun `should build batch_all extrinsic`() = runBlockingTest { val extrinsicBuilder = createExtrinsicBuilder() repeat(2) { @@ -135,7 +135,7 @@ class ExtrinsicBuilderTest { } @Test - fun `should build big extrinsic`() = runBlockingTest{ + fun `should build big extrinsic`() = runBlockingTest { val extrinsicBuilder = createExtrinsicBuilder() repeat(20) { @@ -151,7 +151,7 @@ class ExtrinsicBuilderTest { } @Test - fun `should build single transfer extrinsic statemine`() = runBlockingTest{ + fun `should build single transfer extrinsic statemine`() = runBlockingTest { val runtime = RealRuntimeProvider.buildRuntimeV14("statemine") val extrinsicInHex = @@ -166,10 +166,12 @@ class ExtrinsicBuilderTest { accountId = KEYPAIR.publicKey.publicKeyToSubstrateAccountId(), era = Era.Mortal(64, 59), customSignedExtensions = mapOf( - "ChargeAssetTxPayment" to Struct.Instance( - mapOf( - "tip" to BigInteger.ZERO, - "assetId" to null + "ChargeAssetTxPayment" to SignedExtensionValue( + Struct.Instance( + mapOf( + "tip" to BigInteger.ZERO, + "assetId" to null + ) ) ) ), @@ -186,6 +188,44 @@ class ExtrinsicBuilderTest { assertEquals(extrinsicInHex, encoded) } + @Test + fun `should register signed extensions after creation`() = runBlockingTest { + val runtime = RealRuntimeProvider.buildRuntimeV14("statemine") + + val extrinsicInHex = + "0x45028400fdc41550fb5186d71cae699c31731b3e1baa10680c7bd6b3831a6d222cf4d1680045ba1f9d291fff7dddf36f7ec060405d5e87ac8fab8832cfcc66858e6975141748ce89c41bda6c3a84204d3c6f929b928702168ca38bbed69b172044b599a10ab5038800000a0000bcc5ecf679ebd776866a04c212a4ec5dc45cefab57d7aa858c389844e212693f0700e40b5402" + + val builder = ExtrinsicBuilder( + runtime = runtime, + nonce = 34.toBigInteger(), + runtimeVersion = RuntimeVersion(601, 4), + genesisHash = "48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a".fromHex(), + signer = keypairSigner(), + accountId = KEYPAIR.publicKey.publicKeyToSubstrateAccountId(), + era = Era.Mortal(64, 59), + customSignedExtensions = emptyMap(), + blockHash = "0xdd7532c5c01242696001e57cded1bc1326379059300287552a9c344e5bea1070".fromHex() + ) + + builder.transfer( + recipientAccountId = "GqqKJJZ1MtiWiC6CzNg3g8bawriq6HZioHW1NEpxdf6Q6P5".toAccountId(), + amount = BigInteger("10000000000") + ) + + val chargeAssetTxPaymentValue = Struct.Instance( + mapOf( + "tip" to BigInteger.ZERO, + "assetId" to null + ) + ) + + builder.signedExtra("ChargeAssetTxPayment", chargeAssetTxPaymentValue) + + val encoded = builder.build() + + assertEquals(extrinsicInHex, encoded) + } + private fun createExtrinsicBuilder() = ExtrinsicBuilder( runtime = runtime, signer = keypairSigner(),