From 621a172960bf1d2bce3be8923223e841bdd53aca Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 28 Oct 2018 19:10:51 +0300 Subject: [PATCH] Added txpool #222 --- web3swift.xcodeproj/project.pbxproj | 32 +++- .../xcschemes/web3swift-iOS.xcscheme | 1 + .../SolidityDataWriter.swift | 0 web3swift/ABIv2/SolidityTypes.swift | 6 - web3swift/Convenience/String+Extension.swift | 10 + .../KeystoreManager/EthereumAddress.swift | 7 + web3swift/TxPool/DecoderExtensions.swift | 102 ++++++++++ web3swift/TxPool/DictionaryReader.swift | 109 +++++++++++ web3swift/TxPool/TxPool.swift | 174 ++++++++++++++++++ web3swift/Utils/RLP.swift | 4 +- web3swift/Web3/Web3+JSONRPC.swift | 30 ++- web3swift/Web3/Web3+Methods.swift | 10 + .../BetterABI/SolidityFunctionTests.swift | 16 +- web3swiftTests/TransactionTests.swift | 1 + web3swiftTests/TxPoolTests.swift | 33 ++++ 15 files changed, 498 insertions(+), 37 deletions(-) rename web3swift/{DataManagers => ABIv2}/SolidityDataWriter.swift (100%) create mode 100644 web3swift/TxPool/DecoderExtensions.swift create mode 100644 web3swift/TxPool/DictionaryReader.swift create mode 100644 web3swift/TxPool/TxPool.swift create mode 100644 web3swiftTests/TxPoolTests.swift diff --git a/web3swift.xcodeproj/project.pbxproj b/web3swift.xcodeproj/project.pbxproj index ece1336..209ec2e 100644 --- a/web3swift.xcodeproj/project.pbxproj +++ b/web3swift.xcodeproj/project.pbxproj @@ -34,6 +34,13 @@ 13A24E52216365A4004C48A0 /* EthURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A24E50216365A4004C48A0 /* EthURL.swift */; }; 13A24EE7216F988C004C48A0 /* UInt256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A24EE6216F988C004C48A0 /* UInt256.swift */; }; 13A24EE8216F9899004C48A0 /* UInt256.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A24EE6216F988C004C48A0 /* UInt256.swift */; }; + 13A691612185B7AC00F2D9A9 /* TxPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A691602185B7AC00F2D9A9 /* TxPool.swift */; }; + 13A691672185CA6300F2D9A9 /* DictionaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A691662185CA6300F2D9A9 /* DictionaryReader.swift */; }; + 13A6916921860A1900F2D9A9 /* DecoderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A6916821860A1900F2D9A9 /* DecoderExtensions.swift */; }; + 13A6916A21860A1B00F2D9A9 /* DecoderExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A6916821860A1900F2D9A9 /* DecoderExtensions.swift */; }; + 13A6916B21860A1D00F2D9A9 /* DictionaryReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A691662185CA6300F2D9A9 /* DictionaryReader.swift */; }; + 13A6916C21860A1F00F2D9A9 /* TxPool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A691602185B7AC00F2D9A9 /* TxPool.swift */; }; + 13A6916D21860F3100F2D9A9 /* TxPoolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A691622185B90000F2D9A9 /* TxPoolTests.swift */; }; 13FD60DF21821A1100D73D4E /* SolidityFunctionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FD60DE21821A1100D73D4E /* SolidityFunctionTests.swift */; }; 13FD60F521822FAE00D73D4E /* SolidityDataWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FD60F421822FAE00D73D4E /* SolidityDataWriter.swift */; }; 13FD60F621822FAE00D73D4E /* SolidityDataWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FD60F421822FAE00D73D4E /* SolidityDataWriter.swift */; }; @@ -265,6 +272,10 @@ 134B10CE2182447600113663 /* Int+Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Sequence.swift"; sourceTree = ""; }; 13A24E50216365A4004C48A0 /* EthURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthURL.swift; sourceTree = ""; }; 13A24EE6216F988C004C48A0 /* UInt256.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UInt256.swift; sourceTree = ""; }; + 13A691602185B7AC00F2D9A9 /* TxPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxPool.swift; sourceTree = ""; }; + 13A691622185B90000F2D9A9 /* TxPoolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxPoolTests.swift; sourceTree = ""; }; + 13A691662185CA6300F2D9A9 /* DictionaryReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryReader.swift; sourceTree = ""; }; + 13A6916821860A1900F2D9A9 /* DecoderExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecoderExtensions.swift; sourceTree = ""; }; 13FD60DE21821A1100D73D4E /* SolidityFunctionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidityFunctionTests.swift; sourceTree = ""; }; 13FD60F421822FAE00D73D4E /* SolidityDataWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SolidityDataWriter.swift; sourceTree = ""; }; 1CD91AFC1FD76910007BFB45 /* web3swift_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = web3swift_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -465,12 +476,14 @@ path = Contracts; sourceTree = ""; }; - 13FD60F321822F9700D73D4E /* DataManagers */ = { + 13A6915F2185B78E00F2D9A9 /* TxPool */ = { isa = PBXGroup; children = ( - 13FD60F421822FAE00D73D4E /* SolidityDataWriter.swift */, + 13A691602185B7AC00F2D9A9 /* TxPool.swift */, + 13A691662185CA6300F2D9A9 /* DictionaryReader.swift */, + 13A6916821860A1900F2D9A9 /* DecoderExtensions.swift */, ); - path = DataManagers; + path = TxPool; sourceTree = ""; }; 1CD91AF21FD76910007BFB45 = { @@ -500,7 +513,7 @@ 1CD91AFE1FD76910007BFB45 /* web3swift */ = { isa = PBXGroup; children = ( - 13FD60F321822F9700D73D4E /* DataManagers */, + 13A6915F2185B78E00F2D9A9 /* TxPool */, 13A24EDF216E4BF5004C48A0 /* Contracts */, 13A24E4F21636541004C48A0 /* EthURL */, 81FECD43211ADE20006DA367 /* ObjectiveCbridge */, @@ -546,6 +559,7 @@ E23B5AE220EA69B900DC7F32 /* NumberFormattingUtilTests.swift */, E23B5AE420EA6A0A00DC7F32 /* ContractV2Tests.swift */, 1323B7F32174BB1E00306BBB /* Support.swift */, + 13A691622185B90000F2D9A9 /* TxPoolTests.swift */, 1CD91B331FD769A6007BFB45 /* Info.plist */, ); path = web3swiftTests; @@ -684,6 +698,7 @@ 81C5DA2A2074CA1400424CD6 /* ABIv2Decoding.swift */, 1323B7F62176255900306BBB /* SolidityTypes.swift */, 1323B7AC2170E1ED00306BBB /* SolidityFunction.swift */, + 13FD60F421822FAE00D73D4E /* SolidityDataWriter.swift */, ); path = ABIv2; sourceTree = ""; @@ -1038,11 +1053,13 @@ 8113D2CA1FD7E1590074282C /* KeystoreManager.swift in Sources */, 817EBB1F2006265400E02EAA /* Base58.swift in Sources */, 817EBB2520066E2B00E02EAA /* AbstractKeystore.swift in Sources */, + 13A691612185B7AC00F2D9A9 /* TxPool.swift in Sources */, 81C0FCF720441A1D00D82FAF /* TransactionSigner.swift in Sources */, 8125F06920499AC300A0F2FE /* BloomFilter.swift in Sources */, 810B0F9A1FEC446B00CF0DA2 /* Web3+JSONRPC.swift in Sources */, 81195AB020D7FF8500ABC6B1 /* Promise+Web3+Contract+GetIndexedEvents.swift in Sources */, 1323B8052177B85F00306BBB /* ERC721.swift in Sources */, + 13A6916921860A1900F2D9A9 /* DecoderExtensions.swift in Sources */, 8113D2C61FD7E1590074282C /* LibSecp256k1Extension.swift in Sources */, 81EB1E4B208173D7003BD47F /* Web3+Personal.swift in Sources */, 81A1824B20D7DF1B0016741F /* Promise+Web3+Personal+UnlockAccount.swift in Sources */, @@ -1055,6 +1072,7 @@ 81C5DA252072E14E00424CD6 /* ABIv2Encoding.swift in Sources */, 81A1824220D7AA750016741F /* Promise+Web3+Eth+SendTransaction.swift in Sources */, 817EBB2920075D2E00E02EAA /* BIP39.swift in Sources */, + 13A691672185CA6300F2D9A9 /* DictionaryReader.swift in Sources */, 81FECD49211ADEB1006DA367 /* Web3+Instance+ObjC.swift in Sources */, 815630022007B53C00A0EC2F /* BIP32Keystore.swift in Sources */, 818ABD5B1FE95F8F002657BB /* Web3+Instance.swift in Sources */, @@ -1146,6 +1164,7 @@ 81A1821D20D5C6C10016741F /* PromiseTests.swift in Sources */, 81FECD5E211AEFCE006DA367 /* ObjectiveCTests.swift in Sources */, E23B5ADD20EA685D00DC7F32 /* EIP67Tests.swift in Sources */, + 13A6916D21860F3100F2D9A9 /* TxPoolTests.swift in Sources */, 00E5FE8220EA3FF40030E0D6 /* InfuraTests.swift in Sources */, 1CD91B321FD769A6007BFB45 /* MainTests.swift in Sources */, E23B5ADF20EA68FA00DC7F32 /* SECP256K1Tests.swift in Sources */, @@ -1163,6 +1182,7 @@ 4194811E203630530065A83B /* Web3.swift in Sources */, 134B10D02182447600113663 /* Int+Sequence.swift in Sources */, 4194811F203630530065A83B /* Web3+Instance.swift in Sources */, + 13A6916C21860A1F00F2D9A9 /* TxPool.swift in Sources */, 41948120203630530065A83B /* Web3+Contract.swift in Sources */, 41948121203630530065A83B /* Web3+Utils.swift in Sources */, 8125F06A20499AC300A0F2FE /* BloomFilter.swift in Sources */, @@ -1173,6 +1193,7 @@ 1323B8062177B85F00306BBB /* ERC721.swift in Sources */, 41948125203630530065A83B /* Web3+Methods.swift in Sources */, 81EB1E4C208173D7003BD47F /* Web3+Personal.swift in Sources */, + 13A6916B21860A1D00F2D9A9 /* DictionaryReader.swift in Sources */, 81FECD4D211ADF70006DA367 /* Web3+HttpProvider+ObjC.swift in Sources */, 81D7D97620A3240900A193EC /* EthereumFilterEncodingExtensions.swift in Sources */, 41948126203630530065A83B /* Web3+Eth.swift in Sources */, @@ -1201,6 +1222,7 @@ 8160E5CF20B8245A0070070B /* IBAN.swift in Sources */, 41948131203630530065A83B /* BIP32KeystoreJSONStructure.swift in Sources */, 41948132203630530065A83B /* BIP32HDNode.swift in Sources */, + 13A6916A21860A1B00F2D9A9 /* DecoderExtensions.swift in Sources */, 81A1824920D7DDA20016741F /* Promise+Web3+Personal+Sign.swift in Sources */, 81A1822620D678590016741F /* Promise+Web3+Eth+GetGasPrice.swift in Sources */, 13A24E52216365A4004C48A0 /* EthURL.swift in Sources */, @@ -1421,6 +1443,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_BITCODE = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_GENERATE_TEST_COVERAGE_FILES = NO; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = web3swift/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1460,6 +1483,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_BITCODE = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_GENERATE_TEST_COVERAGE_FILES = NO; HEADER_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = web3swift/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/web3swift.xcodeproj/xcshareddata/xcschemes/web3swift-iOS.xcscheme b/web3swift.xcodeproj/xcshareddata/xcschemes/web3swift-iOS.xcscheme index acab4f7..20010ab 100644 --- a/web3swift.xcodeproj/xcshareddata/xcschemes/web3swift-iOS.xcscheme +++ b/web3swift.xcodeproj/xcshareddata/xcschemes/web3swift-iOS.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + codeCoverageEnabled = "YES" shouldUseLaunchSchemeArgsEnv = "YES"> ) -> Substring { - return self[..) -> Substring { + return self[.. String.Index { + return index(startIndex, offsetBy: i) + } +} + extension String { public func keccak256() -> Data { return data.keccak256() diff --git a/web3swift/KeystoreManager/EthereumAddress.swift b/web3swift/KeystoreManager/EthereumAddress.swift index d626ce6..f7df8b9 100644 --- a/web3swift/KeystoreManager/EthereumAddress.swift +++ b/web3swift/KeystoreManager/EthereumAddress.swift @@ -9,6 +9,10 @@ import BigInt import Foundation +public enum AddressError: Error { + case invalidAddress(String) +} + public struct EthereumAddress: Equatable { public enum AddressType { case normal @@ -88,6 +92,9 @@ public struct EthereumAddress: Equatable { _address = addressData.toHexString().withHex self.type = type } + public func check() throws { + guard isValid else { throw AddressError.invalidAddress(_address) } + } public static var contractDeployment: EthereumAddress { return EthereumAddress("0x", type: .contractDeployment) diff --git a/web3swift/TxPool/DecoderExtensions.swift b/web3swift/TxPool/DecoderExtensions.swift new file mode 100644 index 0000000..fe25afa --- /dev/null +++ b/web3swift/TxPool/DecoderExtensions.swift @@ -0,0 +1,102 @@ +// +// DecoderExtensions.swift +// web3swift-iOS +// +// Created by Dmitry on 28/10/2018. +// Copyright © 2018 Bankex Foundation. All rights reserved. +// + +import Foundation + +// Inspired by https://gist.github.com/mbuchetics/c9bc6c22033014aa0c550d3b4324411a + +struct JSONCodingKeys: CodingKey { + var stringValue: String + + init?(stringValue: String) { + self.stringValue = stringValue + } + + var intValue: Int? + + init?(intValue: Int) { + self.init(stringValue: "\(intValue)") + self.intValue = intValue + } +} + + +extension KeyedDecodingContainer { + + func decode(_ type: Dictionary.Type, forKey key: K) throws -> Dictionary { + let container = try self.nestedContainer(keyedBy: JSONCodingKeys.self, forKey: key) + return try container.decode(type) + } + + func decodeIfPresent(_ type: Dictionary.Type, forKey key: K) throws -> Dictionary? { + guard contains(key) else { + return nil + } + return try decode(type, forKey: key) + } + + func decode(_ type: Array.Type, forKey key: K) throws -> Array { + var container = try self.nestedUnkeyedContainer(forKey: key) + return try container.decode(type) + } + + func decodeIfPresent(_ type: Array.Type, forKey key: K) throws -> Array? { + guard contains(key) else { + return nil + } + return try decode(type, forKey: key) + } + + func decode(_ type: Dictionary.Type) throws -> Dictionary { + var dictionary = Dictionary() + + for key in allKeys { + if let boolValue = try? decode(Bool.self, forKey: key) { + dictionary[key.stringValue] = boolValue + } else if let stringValue = try? decode(String.self, forKey: key) { + dictionary[key.stringValue] = stringValue + } else if let intValue = try? decode(Int.self, forKey: key) { + dictionary[key.stringValue] = intValue + } else if let doubleValue = try? decode(Double.self, forKey: key) { + dictionary[key.stringValue] = doubleValue + } else if let nestedDictionary = try? decode(Dictionary.self, forKey: key) { + dictionary[key.stringValue] = nestedDictionary + } else if let nestedArray = try? decode(Array.self, forKey: key) { + dictionary[key.stringValue] = nestedArray + } + } + return dictionary + } +} + +extension UnkeyedDecodingContainer { + + mutating func decode(_ type: Array.Type) throws -> Array { + var array: [Any] = [] + while isAtEnd == false { + if let value = try? decode(Bool.self) { + array.append(value) + } else if let value = try? decode(Double.self) { + array.append(value) + } else if let value = try? decode(String.self) { + array.append(value) + } else if let nestedDictionary = try? decode(Dictionary.self) { + array.append(nestedDictionary) + } else if let nestedArray = try? decode(Array.self) { + array.append(nestedArray) + } + } + return array + } + + mutating func decode(_ type: Dictionary.Type) throws -> Dictionary { + + let nestedContainer = try self.nestedContainer(keyedBy: JSONCodingKeys.self) + return try nestedContainer.decode(type) + } +} diff --git a/web3swift/TxPool/DictionaryReader.swift b/web3swift/TxPool/DictionaryReader.swift new file mode 100644 index 0000000..95e66e3 --- /dev/null +++ b/web3swift/TxPool/DictionaryReader.swift @@ -0,0 +1,109 @@ +// +// DictionaryReader.swift +// web3swift-iOS +// +// Created by Dmitry on 28/10/2018. +// Copyright © 2018 Bankex Foundation. All rights reserved. +// + +import Foundation +import BigInt + +extension BigUInt { + private typealias Error = DictionaryReader.Error + init(dictionary value: Any) throws { + if let value = value as? String { + if value.isHex { + guard let value = BigUInt(value.withoutHex, radix: 16) else { throw Error.unconvertable } + self = value + } else { + guard let value = BigUInt(value) else { throw Error.unconvertable } + self = value + } + } else if let value = value as? Int { + self = BigUInt(value) + } else { + throw Error.unconvertable + } + } +} + +public class DictionaryReader { + public enum Error: Swift.Error { + case notFound + case unconvertable + } + public var raw: Any + public init(_ data: Any) { + self.raw = data + } + public func at(_ key: String) throws -> DictionaryReader { + guard let data = raw as? [String: Any] else { throw Error.unconvertable } + guard let value = data[key] else { throw Error.notFound } + return DictionaryReader(value) + } + public func dictionary(block: (DictionaryReader, DictionaryReader)throws->()) throws { + guard let data = raw as? [String: Any] else { throw Error.unconvertable } + try data.forEach { + try block(DictionaryReader($0),DictionaryReader($1)) + } + } + public func array(block: (DictionaryReader)throws->()) throws { + guard let data = raw as? [Any] else { throw Error.unconvertable } + try data.forEach { + try block(DictionaryReader($0)) + } + } + + + public func address() throws -> EthereumAddress { + let string = try self.string() + guard string.count >= 42 else { throw Error.unconvertable } + let address = EthereumAddress(String(string[..<42])) + // already checked for size. so don't need to check again for address.isValid + // guard address.isValid else { throw Error.unconvertable } + return address + } + public func string() throws -> String { + if let value = raw as? String { + return value + } else if let value = raw as? Int { + return value.description + } else { + throw Error.unconvertable + } + } + public func data() throws -> Data { + return try string().withoutHex.dataFromHex() + } + public func uint256() throws -> BigUInt { + if let value = raw as? String { + if value.isHex { + guard let value = BigUInt(value.withoutHex, radix: 16) else { throw Error.unconvertable } + return value + } else { + guard let value = BigUInt(value) else { throw Error.unconvertable } + return value + } + } else if let value = raw as? Int { + return BigUInt(value) + } else { + throw Error.unconvertable + } + } + public func int() throws -> Int { + if let value = raw as? Int { + return value + } else if let value = raw as? String { + if value.isHex { + guard let value = Int(value.withoutHex, radix: 16) else { throw Error.unconvertable } + return value + } else { + guard let value = Int(value) else { throw Error.unconvertable } + return value + } + } else { + throw Error.unconvertable + } + } +} diff --git a/web3swift/TxPool/TxPool.swift b/web3swift/TxPool/TxPool.swift new file mode 100644 index 0000000..a34e12b --- /dev/null +++ b/web3swift/TxPool/TxPool.swift @@ -0,0 +1,174 @@ +// +// TxPool.swift +// web3swift-iOS +// +// Created by Dmitry on 28/10/2018. +// Copyright © 2018 Bankex Foundation. All rights reserved. +// + +import Foundation +import PromiseKit +import BigInt + +public class TxPool { + public static var `default`: TxPool { + return TxPool(web3: .default) + } + var web3: Web3 + public init(web3: Web3) { + self.web3 = web3 + } + + public func status() -> Promise { + let request = JSONRPCRequestFabric.prepareRequest(.txPoolStatus, parameters: []) + let rp = web3.dispatch(request) + let queue = web3.requestDispatcher.queue + return rp.map(on: queue ) { try TxPoolStatus($0.response()) } + } + + public func inspect() -> Promise { + let request = JSONRPCRequestFabric.prepareRequest(.txPoolInspect, parameters: []) + let rp = web3.dispatch(request) + let queue = web3.requestDispatcher.queue + return rp.map(on: queue ) { try TxPoolInspect($0.response()) } + } + + public func content() -> Promise { + let request = JSONRPCRequestFabric.prepareRequest(.txPoolContent, parameters: []) + let rp = web3.dispatch(request) + let queue = web3.requestDispatcher.queue + return rp.map(on: queue ) { try TxPoolContent($0.response()) } + } +} + +extension DictionaryReader { + func split(_ separator: String, _ expectedSize: Int) throws -> [DictionaryReader] { + let string = try self.string() + let array = string.components(separatedBy: separator) + guard array.count >= expectedSize else { throw Error.unconvertable } + return array.map { DictionaryReader($0) } + } +} + +public struct TxPoolStatus { + public var pending: Int + public var queued: Int + init(_ dictionary: DictionaryReader) throws { + pending = try dictionary.at("pending").int() + queued = try dictionary.at("queued").int() + } +} + +public struct TxPoolInspect { + public let pending: [InspectedTransaction] + public let queued: [InspectedTransaction] + init(_ dictionary: DictionaryReader) throws { + pending = try TxPoolInspect.parse(dictionary.at("pending")) + queued = try TxPoolInspect.parse(dictionary.at("queued")) + } + init() { + pending = [] + queued = [] + } + private static func parse(_ reader: DictionaryReader) throws -> [InspectedTransaction] { + var array = [InspectedTransaction]() + try reader.dictionary { + let from = try $0.address() + try $1.dictionary { + let nonce = try $0.int() + let transaction = try InspectedTransaction($1, from: from, nonce: nonce) + array.append(transaction) + } + } + return array + } +} + +public struct InspectedTransaction { + public let from: EthereumAddress + public let nonce: Int + public let to: EthereumAddress + public let value: BigUInt + public let gasLimit: BigUInt + public let gasPrice: BigUInt + init(_ reader: DictionaryReader, from: EthereumAddress, nonce: Int) throws { + self.from = from + self.nonce = nonce + let string = try reader.split(" ", 7) + to = try string[0].address() + value = try string[1].uint256() + gasLimit = try string[4].uint256() + gasPrice = try string[7].uint256() + } +} + +public struct TxPoolContent { + public let pending: [TxPoolTransaction] + public let queued: [TxPoolTransaction] + init(_ dictionary: DictionaryReader) throws { + pending = try TxPoolContent.parse(dictionary.at("pending")) + queued = try TxPoolContent.parse(dictionary.at("queued")) + } + init() { + pending = [] + queued = [] + } + private static func parse(_ reader: DictionaryReader) throws -> [TxPoolTransaction] { + var array = [TxPoolTransaction]() + try reader.dictionary { + let from = try $0.address() + try $1.dictionary { + let nonce = try $0.int() + let transaction = try TxPoolTransaction($1, from: from, nonce: nonce) + array.append(transaction) + } + } + return array + } +} + +public struct TxPoolTransaction { + public let from: EthereumAddress + public let nonce: Int + public let to: EthereumAddress + public let value: BigUInt + public let gasLimit: BigUInt + public let gasPrice: BigUInt + public let input: Data + public let hash: Data + public let v: BigUInt + public let r: BigUInt + public let s: BigUInt + public let blockHash: Data + public let transactionIndex: BigUInt + init(_ reader: DictionaryReader, from: EthereumAddress, nonce: Int) throws { + self.from = from + self.nonce = nonce + input = try reader.at("input").data() + gasPrice = try reader.at("gasPrice").uint256() + s = try reader.at("s").uint256() + to = try reader.at("to").address() + value = try reader.at("value").uint256() + gasLimit = try reader.at("gas").uint256() + hash = try reader.at("hash").data() + v = try reader.at("v").uint256() + transactionIndex = try reader.at("transactionIndex").uint256() + r = try reader.at("r").uint256() + blockHash = try reader.at("blockHash").data() + /* some response: + "input" : "0xa9059cbb000000000000000000000000c85780130f5877a501c24702440c9e0bb65dea680000000000000000000000000000000000000000000000000000000000000064", + "gasPrice" : "0x1", + "s" : "0x224219cd7eefea7a6a4affbf85ca96b4183af5b049bed1c0f9c428b31935bac6", + "nonce" : "0xea80", + "to" : "0xd65ef7346144e9ad3b53c69a58b7cb27d02c0ded", + "value" : "0x0", + "gas" : "0xc952", + "from" : "0xa607f816acce53552afb0098d8b0750890b48fbd", + "hash" : "0x403bd25aec9c86593bc8993b1510b55aa17f2c7bff896ef4a71bf2aa7958b14f", + "v" : "0xbf9", + "transactionIndex" : "0x0", + "r" : "0x30caf905be371d4088b9e7a22d940350ff3d7f22fb36a24b558bea7ace6de66e", + "blockHash" : "0x0000000000000000000000000000000000000000000000000000000000000000" */ + } +} + diff --git a/web3swift/Utils/RLP.swift b/web3swift/Utils/RLP.swift index b390675..556766e 100644 --- a/web3swift/Utils/RLP.swift +++ b/web3swift/Utils/RLP.swift @@ -83,7 +83,7 @@ struct RLP { internal static func encodeLength(_ length: BigUInt, offset: UInt8) -> Data? { if length < length56 { - let encodedLength = length + BigUInt(UInt(offset)) + let encodedLength = length + BigUInt(offset) guard encodedLength.bitWidth <= 8 else { return nil } return encodedLength.serialize() } else if length < lengthMax { @@ -116,7 +116,7 @@ struct RLP { encoded.append(prefixData) encoded.append(suffixData) guard encoded.count == 1 else { return nil } - return encoded.bytes[0] + return encoded.first! } public static func encode(_ elements: Array) -> Data? { diff --git a/web3swift/Web3/Web3+JSONRPC.swift b/web3swift/Web3/Web3+JSONRPC.swift index a6e98aa..64fa8c2 100644 --- a/web3swift/Web3/Web3+JSONRPC.swift +++ b/web3swift/Web3/Web3+JSONRPC.swift @@ -71,6 +71,16 @@ public struct JSONRPCresponse: Decodable { public var result: Any? public var error: ErrorMessage? public var message: String? + + public func response() throws -> DictionaryReader { + if let error = error { + throw Web3Error.nodeError(error.message) + } else if let result = result { + return DictionaryReader(result) + } else { + throw Web3Error.nodeError("No response found") + } + } enum JSONRPCresponseKeys: String, CodingKey { case id @@ -91,23 +101,6 @@ public struct JSONRPCresponse: Decodable { public var message: String } - internal var decodableTypes: [Decodable.Type] = [[EventLog].self, - [TransactionDetails].self, - [TransactionReceipt].self, - [Block].self, - [String].self, - [Int].self, - [Bool].self, - EventLog.self, - TransactionDetails.self, - TransactionReceipt.self, - Block.self, - String.self, - Int.self, - Bool.self, - [String: String].self, - [String: Int].self] - public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: JSONRPCresponseKeys.self) let id: Int = try container.decode(Int.self, forKey: .id) @@ -118,6 +111,7 @@ public struct JSONRPCresponse: Decodable { return } var result: Any? + if let rawValue = try? container.decodeIfPresent(String.self, forKey: .result) { result = rawValue } else if let rawValue = try? container.decodeIfPresent(Int.self, forKey: .result) { @@ -150,6 +144,8 @@ public struct JSONRPCresponse: Decodable { result = rawValue } else if let rawValue = try? container.decodeIfPresent([String: Int].self, forKey: .result) { result = rawValue + } else if let rawValue = try? container.decodeIfPresent([String: Any].self, forKey: .result) { + result = rawValue } self.init(id: id, jsonrpc: jsonrpc, result: result, error: nil) } diff --git a/web3swift/Web3/Web3+Methods.swift b/web3swift/Web3/Web3+Methods.swift index 6cadacf..e7d58b0 100644 --- a/web3swift/Web3/Web3+Methods.swift +++ b/web3swift/Web3/Web3+Methods.swift @@ -28,6 +28,10 @@ public enum JSONRPCmethod: String, Encodable { case personalSign = "eth_sign" case unlockAccount = "personal_unlockAccount" case getLogs = "eth_getLogs" + + case txPoolStatus = "txpool_status" + case txPoolInspect = "txpool_inspect" + case txPoolContent = "txpool_content" public var requiredNumOfParameters: Int { switch self { @@ -53,6 +57,12 @@ public enum JSONRPCmethod: String, Encodable { return 0 case .getAccounts: return 0 + case .txPoolStatus: + return 0 + case .txPoolInspect: + return 0 + case .txPoolContent: + return 0 default: return 1 } diff --git a/web3swiftTests/BetterABI/SolidityFunctionTests.swift b/web3swiftTests/BetterABI/SolidityFunctionTests.swift index 3547a08..49258ea 100644 --- a/web3swiftTests/BetterABI/SolidityFunctionTests.swift +++ b/web3swiftTests/BetterABI/SolidityFunctionTests.swift @@ -97,13 +97,13 @@ class SolidityFunctionTests: XCTestCase { func testEncodeAndDecodeString() throws { let user: EthereumAddress = "0x6394b37Cf80A7358b38068f0CA4760ad49983a1B" - let function = try! SolidityFunction(function: "send(address,string,uint256)") + let function = try SolidityFunction(function: "send(address,string,uint256)") let data = function.encode(user, "hello world", 800) let reader = Web3DataResponse(data) - let a = try! reader.address() - let message = try! reader.string() - let b = try! reader.uint256() + let a = try reader.address() + let message = try reader.string() + let b = try reader.uint256() XCTAssertEqual(a, user) XCTAssertEqual(message, "hello world") XCTAssertEqual(b, 800) @@ -114,13 +114,13 @@ class SolidityFunctionTests: XCTestCase { let user: EthereumAddress = "0x6394b37Cf80A7358b38068f0CA4760ad49983a1B" let bigString = "mfiejrh9183yrnv190y3r0m9x17yc90172ymr093 daosjdokamsfl vopadsjnfmowedfoiwlfknlkvkdmf omfp qejfpiqwejfop[w mfewfm qmef fe" - let function = try! SolidityFunction(function: " send ( address, string ,uint256 ) ") + let function = try SolidityFunction(function: " send ( address, string ,uint256 ) ") let data = function.encode(user, bigString, 800) let reader = Web3DataResponse(data) - let a = try! reader.address() - let message = try! reader.string() - let b = try! reader.uint256() + let a = try reader.address() + let message = try reader.string() + let b = try reader.uint256() XCTAssertEqual(a, user) XCTAssertEqual(message, bigString) XCTAssertEqual(b, 800) diff --git a/web3swiftTests/TransactionTests.swift b/web3swiftTests/TransactionTests.swift index b57788e..1b8b93f 100644 --- a/web3swiftTests/TransactionTests.swift +++ b/web3swiftTests/TransactionTests.swift @@ -116,6 +116,7 @@ class TransactionsTests: XCTestCase { let transactionString = "0xa9059cbb00000000000000000000000083b0b52a887a4c05429ee6d4619afeb8007c1a330000000000000000000000000000000000000000000000000001c6bf52634000" let transactionData = Data.fromHex(transactionString)! let rawTransaction = EthereumTransaction.fromRaw(transactionData) + XCTAssertNil(rawTransaction) } // func testTokenBalanceTransferOnMainNetUsingConvenience() throws { diff --git a/web3swiftTests/TxPoolTests.swift b/web3swiftTests/TxPoolTests.swift new file mode 100644 index 0000000..7a8ab58 --- /dev/null +++ b/web3swiftTests/TxPoolTests.swift @@ -0,0 +1,33 @@ +// +// TxPoolTests.swift +// web3swift-iOS_Tests +// +// Created by Dmitry on 28/10/2018. +// Copyright © 2018 Bankex Foundation. All rights reserved. +// + +import XCTest +@testable import web3swift_iOS +import BigInt + +class TxPoolTests: XCTestCase { + override func setUp() { + let url = URL(string: "http://127.0.0.1:8545")! + guard let provider = Web3HttpProvider(url, network: nil, keystoreManager: nil) else { return XCTFail("Please start your local test node") } + Web3.default = Web3(provider: provider) + } + func testTxPoolStatus() throws { + let response = try TxPool.default.status().wait() + print(response) + } + + func testTxPoolInspect() throws { + let response = try TxPool.default.inspect().wait() + print(response) + } + + func testTxPoolContent() throws { + let response = try TxPool.default.content().wait() + print(response) + } +}