Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOS-7779 Add TransactionBuilder #817

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions BlockchainSdk.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@
DA82434127A2B0AD00CFC2C0 /* PolkadotTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA82434027A2B0AD00CFC2C0 /* PolkadotTarget.swift */; };
DA82434327A2B0C100CFC2C0 /* PolkadotResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA82434227A2B0C100CFC2C0 /* PolkadotResponse.swift */; };
DA9EA73F29EE958500CAE6F2 /* CosmosTransactionParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9EA73E29EE958500CAE6F2 /* CosmosTransactionParams.swift */; };
DA9F15F22C80B3B800EA7FAF /* FilecoinTransactionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F15F12C80B3B800EA7FAF /* FilecoinTransactionBuilder.swift */; };
DA9F15F42C80BDC700EA7FAF /* FilecoinTransactionBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F15F32C80BDC700EA7FAF /* FilecoinTransactionBuilderTests.swift */; };
DA9F76E927EC8AEB00F0665C /* TronAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F76E827EC8AEB00F0665C /* TronAddressService.swift */; };
DA9F76EC27EC9A2900F0665C /* TronWalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F76EB27EC9A2900F0665C /* TronWalletManager.swift */; };
DA9F76EF27EC9BCD00F0665C /* TronNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9F76EE27EC9BCD00F0665C /* TronNetworkService.swift */; };
Expand Down Expand Up @@ -583,6 +585,7 @@
DAE657E62BFC732400D7D63A /* value.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE657E22BFC732400D7D63A /* value.pb.swift */; };
DAE657E72BFC732400D7D63A /* token.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE657E32BFC732400D7D63A /* token.pb.swift */; };
DAE657E92BFCA3E400D7D63A /* KoinosTransactionBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE657E82BFCA3E400D7D63A /* KoinosTransactionBuilderTests.swift */; };
DAE864BB2C81CF1700A2D51A /* FilecoinFeeParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE864BA2C81CF1700A2D51A /* FilecoinFeeParameters.swift */; };
DAED18A22C7DF3D900522056 /* FilecoinNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAED18A12C7DF3D900522056 /* FilecoinNetworkService.swift */; };
DAED921F27A150E500F188D7 /* PolkadotAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAED921E27A150E500F188D7 /* PolkadotAddressService.swift */; };
DAF0866E27A942D60024312E /* PolkadotWalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAF0866D27A942D60024312E /* PolkadotWalletManager.swift */; };
Expand Down Expand Up @@ -1478,6 +1481,8 @@
DA82434027A2B0AD00CFC2C0 /* PolkadotTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotTarget.swift; sourceTree = "<group>"; };
DA82434227A2B0C100CFC2C0 /* PolkadotResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotResponse.swift; sourceTree = "<group>"; };
DA9EA73E29EE958500CAE6F2 /* CosmosTransactionParams.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CosmosTransactionParams.swift; sourceTree = "<group>"; };
DA9F15F12C80B3B800EA7FAF /* FilecoinTransactionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinTransactionBuilder.swift; sourceTree = "<group>"; };
DA9F15F32C80BDC700EA7FAF /* FilecoinTransactionBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinTransactionBuilderTests.swift; sourceTree = "<group>"; };
DA9F76E827EC8AEB00F0665C /* TronAddressService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronAddressService.swift; sourceTree = "<group>"; };
DA9F76EB27EC9A2900F0665C /* TronWalletManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronWalletManager.swift; sourceTree = "<group>"; };
DA9F76EE27EC9BCD00F0665C /* TronNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TronNetworkService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1523,6 +1528,7 @@
DAE657E22BFC732400D7D63A /* value.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = value.pb.swift; sourceTree = "<group>"; };
DAE657E32BFC732400D7D63A /* token.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = token.pb.swift; sourceTree = "<group>"; };
DAE657E82BFCA3E400D7D63A /* KoinosTransactionBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KoinosTransactionBuilderTests.swift; sourceTree = "<group>"; };
DAE864BA2C81CF1700A2D51A /* FilecoinFeeParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinFeeParameters.swift; sourceTree = "<group>"; };
DAED18A12C7DF3D900522056 /* FilecoinNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilecoinNetworkService.swift; sourceTree = "<group>"; };
DAED921E27A150E500F188D7 /* PolkadotAddressService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolkadotAddressService.swift; sourceTree = "<group>"; };
DAF0866D27A942D60024312E /* PolkadotWalletManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PolkadotWalletManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3402,6 +3408,8 @@
DAD1565A2C7DCFAD00DE52B3 /* Network */,
DA15D1E92C77830E00FD733B /* FilecoinExternalLinkProvider.swift */,
DA20BD692C7BC5E9000F02DF /* FilecoinWalletAssembly.swift */,
DA9F15F12C80B3B800EA7FAF /* FilecoinTransactionBuilder.swift */,
DAE864BA2C81CF1700A2D51A /* FilecoinFeeParameters.swift */,
);
path = Filecoin;
sourceTree = "<group>";
Expand Down Expand Up @@ -3457,6 +3465,7 @@
isa = PBXGroup;
children = (
DA20BD6C2C7BC7AF000F02DF /* FilecoinAddressTests.swift */,
DA9F15F32C80BDC700EA7FAF /* FilecoinTransactionBuilderTests.swift */,
);
path = Filecoin;
sourceTree = "<group>";
Expand Down Expand Up @@ -4836,6 +4845,7 @@
B0C2C89D25FBCF0200A61622 /* RosettaTarget.swift in Sources */,
B62011042B7AAC2100155235 /* Collection+.swift in Sources */,
EFD717DF2A27310E00E5430D /* AddressType.swift in Sources */,
DAE864BB2C81CF1700A2D51A /* FilecoinFeeParameters.swift in Sources */,
B69F21E62B86CD4A00A1177B /* UnixTimestamp.swift in Sources */,
EFF607C72BD000D000C37210 /* EthereumAddressService.swift in Sources */,
0A7624E32C296969002FA139 /* ICPWalletManager.swift in Sources */,
Expand Down Expand Up @@ -4890,6 +4900,7 @@
5D88C80B256BCDBB00020028 /* StellarTransactionParams.swift in Sources */,
B69824772B7175EA00E1333D /* HederaTransactionParams.swift in Sources */,
5D88838927D3BB8B008744E1 /* WalletError.swift in Sources */,
DA9F15F22C80B3B800EA7FAF /* FilecoinTransactionBuilder.swift in Sources */,
B64A680D2BCE9E82009ED960 /* EthereumOptimisticRollupWalletAssembly.swift in Sources */,
EF72577E2A8D42A100EA8CB2 /* TransactionHistory.swift in Sources */,
DA4B80252BBA66F900CE50B7 /* BitcoinTransactionFeeCalculator.swift in Sources */,
Expand Down Expand Up @@ -5471,6 +5482,7 @@
DA1D3B7B2B57B8FB00247393 /* BigUInt+.swift in Sources */,
2D535E872A0CC5FA0081EB76 /* AddressesValidationTests.swift in Sources */,
DAD62DE62C467718008509BE /* MantleTests.swift in Sources */,
DA9F15F42C80BDC700EA7FAF /* FilecoinTransactionBuilderTests.swift in Sources */,
B62BCBAC2B3D978F007494CF /* VeChainTests.swift in Sources */,
EF0DA78928523FAC0081092A /* LitecoinTests.swift in Sources */,
EF0DA78828523FAC0081092A /* BitcoinTests.swift in Sources */,
Expand Down
15 changes: 15 additions & 0 deletions BlockchainSdk/Blockchains/Filecoin/FilecoinFeeParameters.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// FilecoinFeeParameters.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 30.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import BigInt

struct FilecoinFeeParameters: FeeParameters {
let gasUnitPrice: BigUInt
let gasLimit: Int64
let gasPremium: BigUInt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

По поводу типов данных - я провалидирую корректность в следующем PRе (про WalletManager)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// FilecoinTransactionBuilder.swift
// BlockchainSdk
//
// Created by Aleksei Muraveinik on 29.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import TangemSdk
import WalletCore

enum FilecoinTransactionBuilderError: Error {
case filecoinFeeParametersNotFound
case failedToConvertAmountToBigUInt
case failedToGetDataFromJSON
}

final class FilecoinTransactionBuilder {
private let wallet: Wallet

init(wallet: Wallet) {
self.wallet = wallet
}
m3g0byt3 marked this conversation as resolved.
Show resolved Hide resolved

func buildForSign(transaction: Transaction, nonce: UInt64) throws -> Data {
guard let feeParameters = transaction.fee.parameters as? FilecoinFeeParameters else {
throw FilecoinTransactionBuilderError.filecoinFeeParametersNotFound
}

let input = try makeSigningInput(transaction: transaction, nonce: nonce, feeParameters: feeParameters)
let txInputData = try input.serializedData()

let preImageHashes = TransactionCompiler.preImageHashes(coinType: .filecoin, txInputData: txInputData)
let preSigningOutput = try TxCompilerPreSigningOutput(serializedData: preImageHashes)

return preSigningOutput.dataHash
}

func buildForSend(
transaction: Transaction,
nonce: UInt64,
signatureInfo: SignatureInfo
) throws -> FilecoinSignedTransactionBody {
guard let feeParameters = transaction.fee.parameters as? FilecoinFeeParameters else {
throw FilecoinTransactionBuilderError.filecoinFeeParametersNotFound
}
m3g0byt3 marked this conversation as resolved.
Show resolved Hide resolved

let signatures = DataVector()
signatures.add(data: signatureInfo.signature)

let publicKeys = DataVector()
publicKeys.add(data: try Secp256k1Key(with: signatureInfo.publicKey).decompress())

let input = try makeSigningInput(transaction: transaction, nonce: nonce, feeParameters: feeParameters)
let txInputData = try input.serializedData()

let compiledWithSignatures = TransactionCompiler.compileWithSignatures(
coinType: .filecoin,
txInputData: txInputData,
signatures: signatures,
publicKeys: publicKeys
)

let signingOutput = try FilecoinSigningOutput(serializedData: compiledWithSignatures)

guard let jsonData = signingOutput.json.data(using: .utf8) else {
throw FilecoinTransactionBuilderError.failedToGetDataFromJSON
}

return try JSONDecoder().decode(FilecoinSignedTransactionBody.self, from: jsonData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А там в API прям json будет уходить? обычно просто длинная hex строка

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

private func makeSigningInput(
transaction: Transaction,
nonce: UInt64,
feeParameters: FilecoinFeeParameters
) throws -> FilecoinSigningInput {
guard let value = transaction.amount.bigUIntValue else {
throw FilecoinTransactionBuilderError.failedToConvertAmountToBigUInt
}

return try FilecoinSigningInput.with { input in
input.to = transaction.destinationAddress
input.nonce = nonce

input.value = value.serialize()

input.gasFeeCap = feeParameters.gasUnitPrice.serialize()
input.gasLimit = feeParameters.gasLimit
input.gasPremium = feeParameters.gasPremium.serialize()

input.publicKey = try Secp256k1Key(with: wallet.publicKey.blockchainKey).decompress()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше декомпресснуть один раз и положить в параметры класса

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пофиксил в следующем PR
#818 (comment)

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import Foundation

struct FilecoinSignedTransactionBody: Encodable {
struct Signature: Encodable {
struct FilecoinSignedTransactionBody: Codable, Equatable {
tureck1y marked this conversation as resolved.
Show resolved Hide resolved
struct Signature: Codable, Equatable {
let type: Int
let signature: String

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation

struct FilecoinTransactionBody: Encodable {
struct FilecoinTransactionBody: Codable, Equatable {
let sourceAddress: String
let destinationAddress: String
let amount: String
Expand Down
103 changes: 103 additions & 0 deletions BlockchainSdkTests/Filecoin/FilecoinTransactionBuilderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//
// FilecoinTransactionBuilderTests.swift
// BlockchainSdkTests
//
// Created by Aleksei Muraveinik on 29.08.24.
// Copyright © 2024 Tangem AG. All rights reserved.
//

import XCTest
@testable import BlockchainSdk

final class FilecoinTransactionBuilderTests: XCTestCase {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не увидел теста на размер транзакции, хоть и секп все равно его надо делать

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пофиксил в следующем PR
#818 (comment)

private let transactionBuilder = FilecoinTransactionBuilder(
wallet: Wallet(
blockchain: .filecoin,
addresses: [
.default: PlainAddress(
value: Constants.sourceAddress,
publicKey: Wallet.PublicKey(
seedKey: Constants.publicKey,
derivationType: nil
),
type: .default
)
]
)
)

private var transaction: Transaction {
Transaction(
amount: Amount(
with: .filecoin,
type: .coin,
value: 0.01
),
fee: Fee(
Amount(
with: .filecoin,
type: .coin,
value: (101225 * 1526328) / Blockchain.filecoin.decimalValue
),
parameters: FilecoinFeeParameters(
gasUnitPrice: 101225,
gasLimit: 1526328,
gasPremium: 50612
)
),
sourceAddress: Constants.sourceAddress,
destinationAddress: Constants.destinationAddress,
changeAddress: Constants.sourceAddress
)
}

func testBuildForSign() throws {
let expected = Data(hex: "BEB93CCF5C85273B327AC5DCDD58CBF3066F57FC84B87CD20DC67DF69EC2D0A9")
let actual = try transactionBuilder.buildForSign(transaction: transaction, nonce: 2)

XCTAssertEqual(expected, actual)
}

func testBuildForSend() throws {
let nonce: UInt64 = 2
let expected = FilecoinSignedTransactionBody(
transactionBody: FilecoinTransactionBody(
sourceAddress: Constants.sourceAddress,
destinationAddress: Constants.destinationAddress,
amount: "10000000000000000",
nonce: nonce,
gasUnitPrice: "101225",
gasLimit: 1526328,
gasPremium: "50612"
),
signature: FilecoinSignedTransactionBody.Signature(
type: 1,
signature: "Bogel9o9zvXUT+sC+nVpciGyHfBxWG6V4+xOawP6YrAU1OIbifvEHpRT/Elakv2X6mfUkbQzparvc2HyJBbXRwE="
)
)

let hashToSign = try transactionBuilder.buildForSign(transaction: transaction, nonce: nonce)

let actual = try transactionBuilder.buildForSend(
transaction: transaction,
nonce: nonce,
signatureInfo: SignatureInfo(
signature: Constants.signature,
publicKey: Constants.publicKey,
hash: hashToSign
)
)

XCTAssertEqual(expected, actual)
}
}

private extension FilecoinTransactionBuilderTests {
enum Constants {
static let publicKey = Data(hex: "0374D0F81F42DDFE34114D533E95E6AE5FE6EA271C96F1FA505199FDC365AE9720")
static let signature = Data(hex: "06881E97DA3DCEF5D44FEB02FA75697221B21DF071586E95E3EC4E6B03FA62B014D4E21B89FBC41E9453FC495A92FD97EA67D491B433A5AAEF7361F22416D74701")

static let sourceAddress = "f1flbddhx4vwox3y3ux5bwgsgq2frzeiuvvdrjo7i"
static let destinationAddress = "f1rluskhwvv5b3z36skltu4noszbc5stfihevbf2i"
}
}
Loading