From 2b059e47e99f3deee40038ab70d771ed4a99d639 Mon Sep 17 00:00:00 2001 From: Alexander Osokin <4981841+tureck1y@users.noreply.github.com> Date: Fri, 16 Aug 2024 18:17:16 +0100 Subject: [PATCH] IOS-7662 Refactor Cosmos stakekit builder (#808) --- BlockchainSdk.xcodeproj/project.pbxproj | 4 + .../CosmosStakeKitTransactionHelper.swift | 54 ++++++ .../Cosmos/CosmosTransactionBuilder.swift | 163 +++++++----------- .../Cosmos/CosmosWalletManager.swift | 11 +- 4 files changed, 125 insertions(+), 107 deletions(-) create mode 100644 BlockchainSdk/Blockchains/Cosmos/CosmosStakeKitTransactionHelper.swift diff --git a/BlockchainSdk.xcodeproj/project.pbxproj b/BlockchainSdk.xcodeproj/project.pbxproj index 1f9833380..2d18bfa2d 100644 --- a/BlockchainSdk.xcodeproj/project.pbxproj +++ b/BlockchainSdk.xcodeproj/project.pbxproj @@ -698,6 +698,7 @@ DC667F062C401B97000842D0 /* StakeKitTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC667F052C401B97000842D0 /* StakeKitTransaction.swift */; }; DC667F082C4022CF000842D0 /* SolanaStakeKitTransactionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC667F072C4022CF000842D0 /* SolanaStakeKitTransactionHelper.swift */; }; DC667F0C2C40341C000842D0 /* SolanaStakeKitTransactionHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC667F0B2C40341C000842D0 /* SolanaStakeKitTransactionHelperTests.swift */; }; + DC6CB9E82C6FB11200C21B67 /* CosmosStakeKitTransactionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6CB9E72C6FB11200C21B67 /* CosmosStakeKitTransactionHelper.swift */; }; DC87B9EF2B8F4D9700C61C5A /* BitcoreProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC87B9EC2B8F4D9700C61C5A /* BitcoreProvider.swift */; }; DC87B9F02B8F4D9700C61C5A /* BitcoreResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC87B9ED2B8F4D9700C61C5A /* BitcoreResponse.swift */; }; DC87B9F12B8F4D9700C61C5A /* BitcoreTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC87B9EE2B8F4D9700C61C5A /* BitcoreTarget.swift */; }; @@ -1627,6 +1628,7 @@ DC667F052C401B97000842D0 /* StakeKitTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StakeKitTransaction.swift; sourceTree = ""; }; DC667F072C4022CF000842D0 /* SolanaStakeKitTransactionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolanaStakeKitTransactionHelper.swift; sourceTree = ""; }; DC667F0B2C40341C000842D0 /* SolanaStakeKitTransactionHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolanaStakeKitTransactionHelperTests.swift; sourceTree = ""; }; + DC6CB9E72C6FB11200C21B67 /* CosmosStakeKitTransactionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CosmosStakeKitTransactionHelper.swift; sourceTree = ""; }; DC87B9EC2B8F4D9700C61C5A /* BitcoreProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoreProvider.swift; sourceTree = ""; }; DC87B9ED2B8F4D9700C61C5A /* BitcoreResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoreResponse.swift; sourceTree = ""; }; DC87B9EE2B8F4D9700C61C5A /* BitcoreTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitcoreTarget.swift; sourceTree = ""; }; @@ -3391,6 +3393,7 @@ DA67A69529E51FEA001B6799 /* CosmosWalletManager.swift */, DC0468FB2B87CC6C00C785D1 /* OtherChains */, 0A3BF8E92C69F5D900163492 /* CosmosProtoMessage.swift */, + DC6CB9E72C6FB11200C21B67 /* CosmosStakeKitTransactionHelper.swift */, ); path = Cosmos; sourceTree = ""; @@ -4692,6 +4695,7 @@ 2DDEFBE82B59B44700885675 /* AlgorandProviderTarget.swift in Sources */, B6D7131F2AEBEF750095FE6A /* NEARNetworkResult.APIError.swift in Sources */, B633EA222B8FC5DD00F11BFF /* UTXOTransactionHistoryProvider.swift in Sources */, + DC6CB9E82C6FB11200C21B67 /* CosmosStakeKitTransactionHelper.swift in Sources */, EF2D9E0F2BC43DA80055C485 /* EthereumNetworkProvider.swift in Sources */, DC5E65272B1650F400E81AA5 /* OP_0.swift in Sources */, B6BA93732AEA0E9B00F84E36 /* NEARNetworkParams.Transaction.swift in Sources */, diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosStakeKitTransactionHelper.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosStakeKitTransactionHelper.swift new file mode 100644 index 000000000..5fe85fd45 --- /dev/null +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosStakeKitTransactionHelper.swift @@ -0,0 +1,54 @@ +// +// CosmosStakeKitTransactionHelper.swift +// BlockchainSdk +// +// Created by Alexander Osokin on 16.08.2024. +// Copyright © 2024 Tangem AG. All rights reserved. +// + +import Foundation +import WalletCore + +struct CosmosStakeKitTransactionHelper { + private let builder: CosmosTransactionBuilder + + init(builder: CosmosTransactionBuilder) { + self.builder = builder + } + + func prepareForSign(stakingTransaction: StakeKitTransaction) throws -> Data { + let txInputData = try makeInput(stakingTransaction: stakingTransaction) + return try builder.buildForSignRaw(txInputData: txInputData) + } + + func buildForSend( + stakingTransaction: StakeKitTransaction, + signature: Data + ) throws -> Data { + let txInputData = try makeInput(stakingTransaction: stakingTransaction) + return try builder.buildForSendRaw(txInputData: txInputData, signature: signature) + } + + private func makeInput( + stakingTransaction: StakeKitTransaction + ) throws -> Data { + let stakingProtoMessage = try CosmosProtoMessage(serializedData: Data(hex: stakingTransaction.unsignedData)) + + let feeMessage = stakingProtoMessage.feeAndKeyContainer.feeContainer + let feeValue = feeMessage.feeAmount + + guard let message = CosmosMessage.createStakeMessage(message: stakingProtoMessage.delegateContainer.delegate) else { + throw WalletError.failedToBuildTx + } + + let serializedInput = try builder.serializeInput( + gas: feeMessage.gas, + feeAmount: feeValue.amount, + feeDenomiation: feeValue.denomination, + messages: [message], + memo: nil + ) + + return serializedInput + } +} diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift index 89fea7d45..ec4e8e49e 100644 --- a/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosTransactionBuilder.swift @@ -36,41 +36,16 @@ class CosmosTransactionBuilder { // MARK: Regular transaction func buildForSign(transaction: Transaction) throws -> Data { - let input = try makeInput(transaction: transaction, fee: transaction.fee) - let txInputData = try input.serializedData() - - return try buildForSignInternal(txInputData: txInputData) + let txInputData = try makeInput(transaction: transaction, fee: transaction.fee) + return try buildForSignRaw(txInputData: txInputData) } func buildForSend(transaction: Transaction, signature: Data) throws -> Data { - let input = try makeInput(transaction: transaction, fee: transaction.fee) - let txInputData = try input.serializedData() - - return try buildForSendInternal(txInputData: txInputData, signature: signature) - } - - // MARK: Staking - - func buildForSign(stakingTransaction: StakeKitTransaction) throws -> Data { - let input = try makeInput(stakingTransaction: stakingTransaction) - let txInputData = try input.serializedData() - - return try buildForSignInternal(txInputData: txInputData) + let txInputData = try makeInput(transaction: transaction, fee: transaction.fee) + return try buildForSendRaw(txInputData: txInputData, signature: signature) } - - func buildForSend( - stakingTransaction: StakeKitTransaction, - signature: Data - ) throws -> Data { - let input = try makeInput(stakingTransaction: stakingTransaction) - let txInputData = try input.serializedData() - - return try buildForSendInternal(txInputData: txInputData, signature: signature) - } - - // MARK: Private - private func buildForSignInternal(txInputData: Data) throws -> Data { + func buildForSignRaw(txInputData: Data) throws -> Data { let preImageHashes = TransactionCompiler.preImageHashes(coinType: cosmosChain.coin, txInputData: txInputData) let output = try TxCompilerPreSigningOutput(serializedData: preImageHashes) @@ -81,7 +56,7 @@ class CosmosTransactionBuilder { return output.dataHash } - private func buildForSendInternal(txInputData: Data, signature: Data) throws -> Data { + func buildForSendRaw(txInputData: Data, signature: Data) throws -> Data { let publicKeys = DataVector() publicKeys.add(data: publicKey) @@ -109,7 +84,47 @@ class CosmosTransactionBuilder { return outputData } - private func makeInput(transaction: Transaction, fee: Fee?) throws -> CosmosSigningInput { + func serializeInput( + gas: UInt64, + feeAmount: String, + feeDenomiation: String, + messages: [WalletCore.TW_Cosmos_Proto_Message], + memo: String? + ) throws -> Data { + guard let accountNumber, let sequenceNumber else { + throw WalletError.failedToBuildTx + } + + let fee = CosmosFee.with { fee in + fee.gas = gas + fee.amounts = [ + CosmosAmount.with { amount in + amount.amount = feeAmount + amount.denom = feeDenomiation + } + ] + } + + let input = CosmosSigningInput.with { + $0.mode = .sync + $0.signingMode = .protobuf + $0.accountNumber = accountNumber + $0.chainID = cosmosChain.chainID + $0.sequence = sequenceNumber + $0.publicKey = publicKey + $0.messages = messages + $0.privateKey = Data(repeating: 1, count: 32) + $0.fee = fee + $0.memo = memo ?? "" + } + + let serialized = try input.serializedData() + return serialized + } + + // MARK: Private + + private func makeInput(transaction: Transaction, fee: Fee?) throws -> Data { let decimalValue: Decimal switch transaction.amount.type { case .coin: @@ -161,81 +176,26 @@ class CosmosTransactionBuilder { $0.sendCoinsMessage = sendCoinsMessage } } - - guard - let accountNumber = self.accountNumber, - let sequenceNumber = self.sequenceNumber - else { + + guard let fee, let parameters = fee.parameters as? CosmosFeeParameters else { throw WalletError.failedToBuildTx } - - let params = transaction.params as? CosmosTransactionParams + + let feeAmountInSmallestDenomination = (fee.amount.value * decimalValue).uint64Value let feeDenomination = try feeDenomination(for: transaction.amount) - let input = CosmosSigningInput.with { - $0.mode = .sync - $0.signingMode = .protobuf; - $0.accountNumber = accountNumber - $0.chainID = cosmosChain.chainID - $0.memo = params?.memo ?? "" - $0.sequence = sequenceNumber - $0.messages = [message] - $0.publicKey = publicKey + let params = transaction.params as? CosmosTransactionParams - if let fee, let parameters = fee.parameters as? CosmosFeeParameters { - let feeAmountInSmallestDenomination = (fee.amount.value * decimalValue).uint64Value + let serializedInput = try serializeInput( + gas: parameters.gas, + feeAmount: "\(feeAmountInSmallestDenomination)", + feeDenomiation: feeDenomination, + messages: [message], + memo: params?.memo + ) - $0.fee = CosmosFee.with { - $0.gas = parameters.gas - $0.amounts = [CosmosAmount.with { - $0.amount = "\(feeAmountInSmallestDenomination)" - $0.denom = feeDenomination - }] - } - } - } - - return input + return serializedInput } - - private func makeInput( - stakingTransaction: StakeKitTransaction - ) throws -> CosmosSigningInput { - guard let accountNumber, let sequenceNumber else { - throw WalletError.failedToBuildTx - } - - let stakingProtoMessage = try CosmosProtoMessage(serializedData: Data(hex: stakingTransaction.unsignedData)) - - let feeMessage = stakingProtoMessage.feeAndKeyContainer.feeContainer - let feeValue = feeMessage.feeAmount - - guard let message = CosmosMessage.createStakeMessage(message: stakingProtoMessage.delegateContainer.delegate) else { - throw WalletError.failedToBuildTx - } - let fee = CosmosFee.with { fee in - fee.gas = feeMessage.gas - fee.amounts = [ - CosmosAmount.with { amount in - amount.amount = feeValue.amount - amount.denom = feeValue.denomination - } - ] - } - - let input = CosmosSigningInput.with { - $0.mode = .sync - $0.signingMode = .protobuf - $0.accountNumber = accountNumber - $0.chainID = cosmosChain.chainID - $0.sequence = sequenceNumber - $0.publicKey = publicKey - $0.messages = [message] - $0.privateKey = Data(repeating: 1, count: 32) - $0.fee = fee - } - return input - } - + private func denomination(for amount: Amount) throws -> String { switch amount.type { case .coin: @@ -252,7 +212,6 @@ class CosmosTransactionBuilder { } } - private func feeDenomination(for amount: Amount) throws -> String { switch amount.type { case .coin: diff --git a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift index 9e9b090b6..b4ab328ee 100644 --- a/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift +++ b/BlockchainSdk/Blockchains/Cosmos/CosmosWalletManager.swift @@ -203,8 +203,10 @@ extension CosmosWalletManager: StakeKitTransactionSender { transaction: StakeKitTransaction, signer: any TransactionSigner ) -> AnyPublisher { - Result { - try txBuilder.buildForSign(stakingTransaction: transaction) + let helper = CosmosStakeKitTransactionHelper(builder: txBuilder) + + return Result { + try helper.prepareForSign(stakingTransaction: transaction) } .publisher .withWeakCaptureOf(self) @@ -216,9 +218,8 @@ extension CosmosWalletManager: StakeKitTransactionSender { return try signature.unmarshal(with: manager.wallet.publicKey.blockchainKey, hash: hash).data } } - .withWeakCaptureOf(self) - .tryMap { manager, signature -> Data in - try manager.txBuilder.buildForSend(stakingTransaction: transaction, signature: signature) + .tryMap { signature -> Data in + try helper.buildForSend(stakingTransaction: transaction, signature: signature) } .withWeakCaptureOf(self) .flatMap { manager, transaction in