diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 5a2ab8863..ca79f26d6 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -379,7 +379,7 @@ extension DefaultContractProtocol { public func decodeEthError(_ data: Data) -> [String: Any]? { guard data.count >= 4, - let err = errors.first(where: { $0.value.methodEncoding == data[0..<4] })?.value else { + let err = errors.first(where: { $0.value.selectorEncoded == data[0..<4] })?.value else { return nil } return err.decodeEthError(data[4...]) @@ -398,7 +398,7 @@ extension DefaultContractProtocol { throw Web3Error.inputError(desc: "RPC failed: contract is missing an address.") } guard let data = self.method(method, parameters: parameters, extraData: nil) else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to encode method \(method) with given parameters: \(String(describing: parameters))") } let transaction = CodableTransaction(to: address, data: data) diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 4c58f08e7..55e6fa78b 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -207,7 +207,7 @@ extension ABI.Element.Function { public func encodeParameters(_ parameters: [Any]) -> Data? { guard parameters.count == inputs.count, let data = ABIEncoder.encode(types: inputs, values: parameters) else { return nil } - return methodEncoding + data + return selectorEncoded + data } } @@ -300,16 +300,18 @@ extension ABI.Element.EthError { /// - data: bytes returned by a function call that stripped error signature hash. /// - Returns: a dictionary containing decoded data mappend to indices and names of returned values or nil if decoding failed. public func decodeEthError(_ data: Data) -> [String: Any]? { - guard inputs.count * 32 <= data.count, + guard data.count > 0, + data.count % 32 == 0, + inputs.count * 32 <= data.count, let decoded = ABIDecoder.decode(types: inputs, data: data) else { return nil } var result = [String: Any]() - for (index, out) in inputs.enumerated() { + for (index, input) in inputs.enumerated() { result["\(index)"] = decoded[index] - if !out.name.isEmpty { - result[out.name] = decoded[index] + if !input.name.isEmpty { + result[input.name] = decoded[index] } } return result @@ -372,7 +374,7 @@ extension ABI.Element { extension ABI.Element.Function { public func decodeInputData(_ rawData: Data) -> [String: Any]? { - return ABIDecoder.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs) + return ABIDecoder.decodeInputData(rawData, methodEncoding: selectorEncoded, inputs: inputs) } /// Decodes data returned by a function call. @@ -520,8 +522,8 @@ extension ABIDecoder { /// - Returns: decoded dictionary of input arguments mapped to their indices and arguments' names if these are not empty. /// If decoding of at least one argument fails, `rawData` size is invalid or `methodEncoding` doesn't match - `nil` is returned. static func decodeInputData(_ rawData: Data, - methodEncoding: Data? = nil, - inputs: [ABI.Element.InOut]) -> [String: Any]? { + methodEncoding: Data? = nil, + inputs: [ABI.Element.InOut]) -> [String: Any]? { let data: Data let sig: Data? diff --git a/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift b/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift index 390b928ee..84bf3f0bc 100644 --- a/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift +++ b/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift @@ -28,7 +28,7 @@ public extension Sequence where Element == ABI.Element { for case let .function(function) in self where function.name != nil { appendFunction(function.name!, function) appendFunction(function.signature, function) - appendFunction(function.methodString.addHexPrefix().lowercased(), function) + appendFunction(function.selector.addHexPrefix().lowercased(), function) /// ABI cannot have two functions with exactly the same name and input arguments if (functions[function.signature]?.count ?? 0) > 1 { diff --git a/Sources/Web3Core/EthereumAddress/EthereumAddress.swift b/Sources/Web3Core/EthereumAddress/EthereumAddress.swift index ddc54b963..865ebcb5c 100755 --- a/Sources/Web3Core/EthereumAddress/EthereumAddress.swift +++ b/Sources/Web3Core/EthereumAddress/EthereumAddress.swift @@ -45,7 +45,7 @@ public struct EthereumAddress: Equatable { } } - /// Checksummed address with `0x` HEX prefix. + /// Checksummed address with `0x` hex prefix. /// If the ``type`` is ``EthereumAddress/AddressType/contractDeployment`` only `0x` prefix is returned. public var address: String { switch self.type { diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index c25ee496d..78e84006a 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -97,24 +97,16 @@ extension APIRequest { data = try await send(uRLRequest: uRLRequest, with: provider.session) } catch Web3Error.rpcError(let error) { let responseAsString = try checkError(method: method, error: error) + // TODO: why do we try to cast error response to Result.self? guard let LiteralType = Result.self as? LiteralInitiableFromString.Type, let literalValue = LiteralType.init(from: responseAsString), let result = literalValue as? Result else { - throw Web3Error.dataError + throw Web3Error.rpcError(error) } return APIResponse(id: 2, result: result) } - /// Checks if `Result` type can be initialized from HEX-encoded bytes. - /// If it can - we attempt initializing a value of `Result` type. - if let LiteralType = Result.self as? LiteralInitiableFromString.Type { - guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { throw Web3Error.dataError } - guard let literalValue = LiteralType.init(from: responseAsString.result) else { throw Web3Error.dataError } - /// `literalValue` conforms `LiteralInitiableFromString` (which conforms to an `APIResponseType` type) so it never fails. - guard let result = literalValue as? Result else { throw Web3Error.typeError } - return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: result) - } - return try JSONDecoder().decode(APIResponse.self, from: data) + return try initalizeLiteralTypeApiResponse(data) ?? JSONDecoder().decode(APIResponse.self, from: data) } public static func send(uRLRequest: URLRequest, with session: URLSession) async throws -> Data { @@ -145,6 +137,26 @@ extension APIRequest { return data } + + /// Attempts decoding and parsing of a response into literal types as `Data`, `(U)Int`, `Big(U)Int`. + /// - Parameter data: response to parse. + /// - Returns: parsed response or `nil`. Throws ``Web3Error``. + internal static func initalizeLiteralTypeApiResponse(_ data: Data) throws -> APIResponse? { + guard let LiteralType = Result.self as? LiteralInitiableFromString.Type else { + return nil + } + guard let responseAsString = try? JSONDecoder().decode(APIResponse.self, from: data) else { + throw Web3Error.dataError(desc: "Failed to decode received data as `APIResponse`. Given data: \(data.toHexString().addHexPrefix()).") + } + guard let literalValue = LiteralType.init(from: responseAsString.result) else { + throw Web3Error.dataError(desc: "Failed to initialize \(LiteralType.self) from String `\(responseAsString.result)`.") + } + /// `literalValue` conforms `LiteralInitiableFromString`, that conforming to an `APIResultType` type, so it's never fails. + guard let result = literalValue as? Result else { + throw Web3Error.typeError(desc: "Initialized value of type \(LiteralType.self) cannot be cast as \(Result.self).") + } + return APIResponse(id: responseAsString.id, jsonrpc: responseAsString.jsonrpc, result: result) + } } /// JSON RPC Error object. See official specification https://www.jsonrpc.org/specification#error_object diff --git a/Sources/Web3Core/KeystoreManager/EtherscanTransactionChecker.swift b/Sources/Web3Core/KeystoreManager/EtherscanTransactionChecker.swift index 1b0353285..1bfa4d270 100644 --- a/Sources/Web3Core/KeystoreManager/EtherscanTransactionChecker.swift +++ b/Sources/Web3Core/KeystoreManager/EtherscanTransactionChecker.swift @@ -12,17 +12,17 @@ public struct EtherscanTransactionChecker: TransactionChecker { public init(urlSession: URLSession, apiKey: String) { self.urlSession = URLSessionProxyImplementation(urlSession: urlSession) - self.apiKey = apiKey + self.apiKey = apiKey.trim() } internal init(urlSession: URLSessionProxy, apiKey: String) { self.urlSession = urlSession - self.apiKey = apiKey + self.apiKey = apiKey.trim() } public func hasTransactions(ethereumAddress: EthereumAddress) async throws -> Bool { let urlString = "https://api.etherscan.io/api?module=account&action=txlist&address=\(ethereumAddress.address)&startblock=0&page=1&offset=1&sort=asc&apikey=\(apiKey)" - guard let url = URL(string: urlString) else { + guard let url = URL(string: urlString), !apiKey.isEmpty else { throw EtherscanTransactionCheckerError.invalidUrl(url: urlString) } let request = URLRequest(url: url) diff --git a/Sources/Web3Core/Structure/Block/Block.swift b/Sources/Web3Core/Structure/Block/Block.swift index 1cb587d2e..a8f688378 100644 --- a/Sources/Web3Core/Structure/Block/Block.swift +++ b/Sources/Web3Core/Structure/Block/Block.swift @@ -98,7 +98,9 @@ extension Block: Decodable { let unclesStrings = try container.decode([String].self, forKey: .uncles) self.uncles = try unclesStrings.map { - guard let data = Data.fromHex($0) else { throw Web3Error.dataError } + guard let data = Data.fromHex($0) else { + throw Web3Error.dataError(desc: "Failed to parse uncle block from hex string to Data. Given string is not valid hex: \($0).") + } return data } } diff --git a/Sources/Web3Core/Structure/EventLog/EventLog.swift b/Sources/Web3Core/Structure/EventLog/EventLog.swift index b68052bcf..475551dc5 100644 --- a/Sources/Web3Core/Structure/EventLog/EventLog.swift +++ b/Sources/Web3Core/Structure/EventLog/EventLog.swift @@ -63,7 +63,9 @@ public struct EventLog: Decodable { let topicsStrings = try container.decode([String].self, forKey: .topics) self.topics = try topicsStrings.map { - guard let topic = Data.fromHex($0) else { throw Web3Error.dataError } + guard let topic = Data.fromHex($0) else { + throw Web3Error.dataError(desc: "Failed to parse event's topic from hex string to Data. Given string is not valid hex: \($0).") + } return topic } } diff --git a/Sources/Web3Core/Structure/Transaction/TransactionInBlock.swift b/Sources/Web3Core/Structure/Transaction/TransactionInBlock.swift index 863268d90..4f50dd492 100644 --- a/Sources/Web3Core/Structure/Transaction/TransactionInBlock.swift +++ b/Sources/Web3Core/Structure/Transaction/TransactionInBlock.swift @@ -15,7 +15,9 @@ public enum TransactionInBlock: Decodable { public init(from decoder: Decoder) throws { let value = try decoder.singleValueContainer() if let string = try? value.decode(String.self) { - guard let d = Data.fromHex(string) else {throw Web3Error.dataError} + guard let d = Data.fromHex(string) else { + throw Web3Error.dataError(desc: "Failed to parse hex string to bytes. Given hex string: \(string)") + } self = .hash(d) } else if let transaction = try? value.decode(CodableTransaction.self) { self = .transaction(transaction) diff --git a/Sources/Web3Core/Structure/TxPool/TxPoolContent.swift b/Sources/Web3Core/Structure/TxPool/TxPoolContent.swift index 6e4e57734..255ce8c3f 100644 --- a/Sources/Web3Core/Structure/TxPool/TxPoolContent.swift +++ b/Sources/Web3Core/Structure/TxPool/TxPoolContent.swift @@ -23,14 +23,14 @@ public struct TxPoolContent: Decodable { for addressKey in raw.allKeys { let addressString = addressKey.stringValue guard let address = EthereumAddress(addressString, type: .normal, ignoreChecksum: true) else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to initialize EthereumAddress from value \(addressString). Is it 20 bytes hex string?") } let nestedContainer = try raw.nestedContainer(keyedBy: AdditionalDataCodingKeys.self, forKey: addressKey) var perNonceInformation = [TxPoolContentForNonce]() perNonceInformation.reserveCapacity(nestedContainer.allKeys.count) for nonceKey in nestedContainer.allKeys { guard let nonce = BigUInt(nonceKey.stringValue) else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to parse \(nonceKey.stringValue) as BigUInt nonce value for address \(addressString).") } let n = try? nestedContainer.nestedUnkeyedContainer(forKey: nonceKey) if n != nil { diff --git a/Sources/Web3Core/Transaction/CodableTransaction.swift b/Sources/Web3Core/Transaction/CodableTransaction.swift index 806e2a36f..3e873d58e 100644 --- a/Sources/Web3Core/Transaction/CodableTransaction.swift +++ b/Sources/Web3Core/Transaction/CodableTransaction.swift @@ -210,7 +210,9 @@ extension CodableTransaction: Codable { /// initializer required to support the Decodable protocol /// - Parameter decoder: the decoder stream for the input data public init(from decoder: Decoder) throws { - guard let env = try EnvelopeFactory.createEnvelope(from: decoder) else { throw Web3Error.dataError } + guard let env = try EnvelopeFactory.createEnvelope(from: decoder) else { + throw Web3Error.dataError(desc: "EnvelopeFactory.createEnvelope failed. Failed to decode given data into CodableTransaction.") + } self.envelope = env // capture any metadata that might be present diff --git a/Sources/Web3Core/Transaction/Envelope/EIP1559Envelope.swift b/Sources/Web3Core/Transaction/Envelope/EIP1559Envelope.swift index 4334a668c..8d1ddd31e 100644 --- a/Sources/Web3Core/Transaction/Envelope/EIP1559Envelope.swift +++ b/Sources/Web3Core/Transaction/Envelope/EIP1559Envelope.swift @@ -102,14 +102,16 @@ extension EIP1559Envelope { let list = try? container.decode([AccessListEntry].self, forKey: .accessList) self.accessList = list ?? [] - let toString = try? container.decode(String.self, forKey: .to) - switch toString { + let stringValue = try? container.decode(String.self, forKey: .to) + switch stringValue { case nil, "0x", "0x0": self.to = EthereumAddress.contractDeploymentAddress() default: // the forced unwrap here is safe as we trap nil in the previous case // swiftlint:disable force_unwrapping - guard let ethAddr = EthereumAddress(toString!) else { throw Web3Error.dataError } + guard let ethAddr = EthereumAddress(stringValue!) else { + throw Web3Error.dataError(desc: "Failed to parse string as EthereumAddress. Given string: \(stringValue!). Is it a valid hex value 20 bytes in length?") + } // swiftlint:enable force_unwrapping self.to = ethAddr } diff --git a/Sources/Web3Core/Transaction/Envelope/EIP2930Envelope.swift b/Sources/Web3Core/Transaction/Envelope/EIP2930Envelope.swift index 5034e4e76..800dc3807 100644 --- a/Sources/Web3Core/Transaction/Envelope/EIP2930Envelope.swift +++ b/Sources/Web3Core/Transaction/Envelope/EIP2930Envelope.swift @@ -80,14 +80,16 @@ extension EIP2930Envelope { let list = try? container.decode([AccessListEntry].self, forKey: .accessList) self.accessList = list ?? [] - let toString = try? container.decode(String.self, forKey: .to) - switch toString { + let stringValue = try? container.decode(String.self, forKey: .to) + switch stringValue { case nil, "0x", "0x0": self.to = EthereumAddress.contractDeploymentAddress() default: // the forced unwrap here is safe as we trap nil in the previous case // swiftlint:disable force_unwrapping - guard let ethAddr = EthereumAddress(toString!) else { throw Web3Error.dataError } + guard let ethAddr = EthereumAddress(stringValue!) else { + throw Web3Error.dataError(desc: "Failed to parse string as EthereumAddress. Given string: \(stringValue!). Is it a valid hex value 20 bytes in length?") + } // swiftlint:enable force_unwrapping self.to = ethAddr } @@ -241,21 +243,25 @@ public struct AccessListEntry: CustomStringConvertible, Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let addrString = try? container.decode(String.self, forKey: .address) - switch addrString { + let stringValue = try? container.decode(String.self, forKey: .address) + switch stringValue { case nil, "0x", "0x0": self.address = EthereumAddress.contractDeploymentAddress() default: // the forced unwrap here is safe as we trap nil in the previous case // swiftlint:disable force_unwrapping - guard let ethAddr = EthereumAddress(addrString!) else { throw Web3Error.dataError } + guard let ethAddr = EthereumAddress(stringValue!) else { + throw Web3Error.dataError(desc: "Failed to parse string as EthereumAddress. Given string: \(stringValue!). Is it a valid hex value 20 bytes in length?") + } // swiftlint:enable force_unwrapping self.address = ethAddr } self.storageKeys = [] if let keyStrings = try? container.decode([String].self, forKey: .storageKeys) { for keyString in keyStrings { - guard let number = BigUInt(from: keyString) else { throw Web3Error.dataError } + guard let number = BigUInt(from: keyString) else { + throw Web3Error.dataError(desc: "Failed to patse storage key values string into BigUInt. String value: \(keyString). Check the formmating of your values.") + } self.storageKeys.append(number) } } diff --git a/Sources/Web3Core/Transaction/Envelope/EnvelopeFactory.swift b/Sources/Web3Core/Transaction/Envelope/EnvelopeFactory.swift index 4e2f22749..441440a88 100644 --- a/Sources/Web3Core/Transaction/Envelope/EnvelopeFactory.swift +++ b/Sources/Web3Core/Transaction/Envelope/EnvelopeFactory.swift @@ -51,10 +51,10 @@ public struct EnvelopeFactory { let envelopeType: TransactionType if container.contains(.type) { let typeUInt = try container.decodeHex(UInt.self, forKey: .type) - if typeUInt < TransactionType.allCases.count { - guard let type = TransactionType(rawValue: typeUInt) else { throw Web3Error.dataError } // conversion error - envelopeType = type - } else { throw Web3Error.dataError } // illegal value + guard let type = TransactionType(rawValue: typeUInt) else { + throw Web3Error.dataError(desc: "Given TransactionType raw value is not supported. Value given is \(typeUInt).") + } + envelopeType = type } else { envelopeType = .legacy } // legacy streams may not have type set switch envelopeType { diff --git a/Sources/Web3Core/Transaction/Envelope/LegacyEnvelope.swift b/Sources/Web3Core/Transaction/Envelope/LegacyEnvelope.swift index a33df7c6e..2e9744aa5 100644 --- a/Sources/Web3Core/Transaction/Envelope/LegacyEnvelope.swift +++ b/Sources/Web3Core/Transaction/Envelope/LegacyEnvelope.swift @@ -89,14 +89,16 @@ extension LegacyEnvelope { self.explicitChainID = try container.decodeHexIfPresent(BigUInt.self, forKey: .chainId) self.nonce = try container.decodeHex(BigUInt.self, forKey: .nonce) - let toString = try? container.decode(String.self, forKey: .to) - switch toString { + let stringValue = try? container.decode(String.self, forKey: .to) + switch stringValue { case nil, "0x", "0x0": self.to = EthereumAddress.contractDeploymentAddress() default: // the forced unwrap here is safe as we trap nil in the previous case // swiftlint:disable force_unwrapping - guard let ethAddr = EthereumAddress(toString!) else { throw Web3Error.dataError } + guard let ethAddr = EthereumAddress(stringValue!) else { + throw Web3Error.dataError(desc: "Failed to parse string as EthereumAddress. Given string: \(stringValue!). Is it a valid hex value 20 bytes in length?") + } // swiftlint:enable force_unwrapping self.to = ethAddr } diff --git a/Sources/Web3Core/Utility/Data+Extension.swift b/Sources/Web3Core/Utility/Data+Extension.swift index 3a8185d0e..d6a6515e2 100755 --- a/Sources/Web3Core/Utility/Data+Extension.swift +++ b/Sources/Web3Core/Utility/Data+Extension.swift @@ -14,19 +14,19 @@ public extension Data { } func toArray(type: T.Type) throws -> [T] { - return try self.withUnsafeBytes { (body: UnsafeRawBufferPointer) in + return try withUnsafeBytes { (body: UnsafeRawBufferPointer) in if let bodyAddress = body.baseAddress, body.count > 0 { let pointer = bodyAddress.assumingMemoryBound(to: T.self) return [T](UnsafeBufferPointer(start: pointer, count: self.count/MemoryLayout.stride)) } else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "`withUnsafeBytes` function call failed. We were unable to get a pointer to the first byte of the buffer or the buffer length was 0.") } } } func constantTimeComparisonTo(_ other: Data?) -> Bool { - guard let rhs = other else {return false} - guard self.count == rhs.count else {return false} + guard let rhs = other else { return false } + guard self.count == rhs.count else { return false } var difference = UInt8(0x00) for i in 0..(_ type: T.Type, forKey: KeyedDecodingContainer.Key) throws -> T { - let hexString = try self.decode(String.self, forKey: forKey) - guard let value = T(from: hexString) else { throw Web3Error.dataError } + let hexString = try decode(String.self, forKey: forKey) + guard let value = T(from: hexString) else { + throw Web3Error.dataError(desc: "Unable to decode given hex string into type `\(T.self)`. Hex string given: `\(hexString)`.") + } return value } - /// Decodes a value of the given key from Hex to `[DecodableFromHex]` + /// Decodes a value of the given key from hex to `[DecodableFromHex]` /// /// Currently this method supports only `Data.Type`, `BigUInt.Type`, `Date.Type`, `UInt.Type` /// /// - Parameter type: Array of a generic type `T` which conforms to `DecodableFromHex` protocol /// - Parameter key: The key that the decoded value is associated with. /// - Returns: A decoded value of type `[T]` - /// - throws: `Web3Error.dataError` if value associated with key are unable to be initialized as `[[DecodableFromHex]]`. public func decodeHex(_ type: [T].Type, forKey: KeyedDecodingContainer.Key) throws -> [T] { var container = try nestedUnkeyedContainer(forKey: forKey) - guard let array = try? container.decodeHex(type) else { throw Web3Error.dataError } - return array + return try container.decodeHex(type) } - /// Decodes a value of the given key from Hex to `[[DecodableFromHex]]` + /// Decodes a value of the given key from hex to `[[DecodableFromHex]]` /// /// Currently this method supports only `Data.Type`, `BigUInt.Type`, `Date.Type`, `EthereumAddress`, `UInt.Type` /// @@ -61,11 +61,10 @@ extension KeyedDecodingContainer { /// - throws: `Web3Error.dataError` if value associated with key are unable to be initialized as `[[DecodableFromHex]]`. public func decodeHex(_ type: [[T]].Type, forKey: KeyedDecodingContainer.Key) throws -> [[T]] { var container = try nestedUnkeyedContainer(forKey: forKey) - guard let array = try? container.decodeHex(type) else { throw Web3Error.dataError } - return array + return try container.decodeHex(type) } - /// Decodes a value of the given key from Hex to `DecodableFromHex` + /// Decodes a value of the given key from hex to `DecodableFromHex` /// /// Currently this method supports only `Data.Type`, `BigUInt.Type`, `Date.Type`, `UInt.Type` /// @@ -91,14 +90,18 @@ extension UnkeyedDecodingContainer { mutating func decodeHex(_ type: [T].Type) throws -> [T] { var array: [T] = [] while !isAtEnd { - let hexString = try decode(String.self) - guard let item = T(from: hexString) else { continue } - array.append(item) + do { + let hexString = try decode(String.self) + guard let item = T(from: hexString) else { continue } + array.append(item) + } catch (let error) { + throw Web3Error.dataError(desc: "Unable to decode UnkeyedDecodingContainer as hex string. Thrown error: \(error.localizedDescription).") + } } return array } - /// Decodes a unkeyed value from Hex to `DecodableFromHex` + /// Decodes a unkeyed value from hex to `DecodableFromHex` /// /// Currently this method supports only `Data.Type`, `BigUInt.Type`, `Date.Type`, `EthereumAddress` /// diff --git a/Sources/Web3Core/Utility/RIPEMD160+StackOveflow.swift b/Sources/Web3Core/Utility/RIPEMD160+StackOveflow.swift index dfb164baf..2d9d027d9 100755 --- a/Sources/Web3Core/Utility/RIPEMD160+StackOveflow.swift +++ b/Sources/Web3Core/Utility/RIPEMD160+StackOveflow.swift @@ -323,7 +323,7 @@ public struct RIPEMD160 { let pointer = bodyAddress.assumingMemoryBound(to: Void.self) _ = memcpy(&X, pointer, 64) } else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "`withUnsafeBytes` function call failed. We were unable to get a pointer to the first byte of the buffer or the buffer length was 0.") } } compress(X) @@ -340,7 +340,7 @@ public struct RIPEMD160 { // Save remaining unprocessed bytes: buffer = Data(bytes: ptr, count: length) } else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to get access to the raw bytes in the given data's buffer. `withUnsafeBytes` function call failed. Either pointer to the first byte of the provided data is `nil` or the data length was 0.") } } count += Int64(data.count) @@ -355,7 +355,7 @@ public struct RIPEMD160 { let pointer = bodyAddress.assumingMemoryBound(to: Void.self) _ = memcpy(&X, pointer, buffer.count) } else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "`withUnsafeBytes` function call failed. We were unable to get a pointer to the first byte of the buffer or the buffer length was 0.") } } @@ -382,7 +382,7 @@ public struct RIPEMD160 { pointer[3] = MDbuf.3 pointer[4] = MDbuf.4 } else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "`withUnsafeMutableBytes` function call failed. We were unable to get a pointer to the first byte of the buffer or the buffer length was 0.") } } diff --git a/Sources/Web3Core/Utility/String+Extension.swift b/Sources/Web3Core/Utility/String+Extension.swift index 778558166..694e34402 100755 --- a/Sources/Web3Core/Utility/String+Extension.swift +++ b/Sources/Web3Core/Utility/String+Extension.swift @@ -84,8 +84,8 @@ extension String { return self } - /// Strips leading zeroes from a HEX string. - /// ONLY HEX string format is supported. + /// Strips leading zeroes from a hex string. + /// ONLY hex string format is supported. /// - Returns: string with stripped leading zeroes (and 0x prefix) or unchanged string. func stripLeadingZeroes() -> String { let hex = addHexPrefix() diff --git a/Sources/Web3Core/Web3Error/Web3Error.swift b/Sources/Web3Core/Web3Error/Web3Error.swift index 5b6770ea2..a37822b6c 100644 --- a/Sources/Web3Core/Web3Error/Web3Error.swift +++ b/Sources/Web3Core/Web3Error/Web3Error.swift @@ -8,22 +8,22 @@ import Foundation public enum Web3Error: LocalizedError { - case transactionSerializationError - case connectionError + case transactionSerializationError(desc: String? = nil) + case connectionError(desc: String? = nil) - case dataError - case typeError + case dataError(desc: String? = nil) + case typeError(desc: String? = nil) case valueError(desc: String? = nil) case serverError(code: Int) case clientError(code: Int) - case walletError + case walletError(desc: String? = nil) case inputError(desc: String) case nodeError(desc: String) case processingError(desc: String) case keystoreError(err: AbstractKeystoreError) case generalError(err: Error) - case unknownError + case unknownError(desc: String? = nil, err: Error? = nil) case rpcError(JsonRpcErrorObject.RpcError) case revert(String, reason: String?) @@ -32,14 +32,14 @@ public enum Web3Error: LocalizedError { public var errorDescription: String? { switch self { - case .transactionSerializationError: - return "Transaction Serialization Error" - case .connectionError: - return "Connection Error" - case .dataError: - return "Data Error" - case .walletError: - return "Wallet Error" + case .transactionSerializationError(let desc): + return "Transaction Serialization Error. Description: \(desc ?? "-")." + case .connectionError(let desc): + return "Connection Error. Description: \(desc ?? "-")." + case .dataError(let desc): + return "Data Error. Description: \(desc ?? "-")." + case .walletError(let desc): + return "Wallet Error. Description: \(desc ?? "-")." case .inputError(let desc): return desc case .nodeError(let desc): @@ -50,10 +50,10 @@ public enum Web3Error: LocalizedError { return err.localizedDescription case .generalError(let err): return err.localizedDescription - case .unknownError: - return "Unknown Error" - case .typeError: - return "Unsupported type" + case let .unknownError(desc, err): + return "Unknown Error. Description: \(desc ?? "-"); Error thrown: \(err?.localizedDescription ?? "-")." + case .typeError(let desc): + return "Unsupported type. Description: \(desc ?? "-")." case let .serverError(code: code): return "Server error: \(code)" case let .clientError(code: code): diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift index 8d695cba2..f80e85c65 100644 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift @@ -145,10 +145,7 @@ public extension IEth { func send(raw data: Data) async throws -> TransactionSendingResult { guard let transaction = CodableTransaction(rawValue: data) else { - // FIXME: When the PR is merged add this description to dataError -> - // Description to add: - // Link to PR: - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to decode raw bytes into CodableTransaction. Given bytes are \(data.toHexString()).") } let request = APIRequest.sendRawTransaction(data.toHexString().addHexPrefix()) let response: APIResponse = try await APIRequest.sendRequest(with: provider, for: request) diff --git a/Sources/web3swift/HookedFunctions/Web3+Wallet.swift b/Sources/web3swift/HookedFunctions/Web3+Wallet.swift index 18f36fe84..c5b23e403 100755 --- a/Sources/web3swift/HookedFunctions/Web3+Wallet.swift +++ b/Sources/web3swift/HookedFunctions/Web3+Wallet.swift @@ -13,12 +13,10 @@ extension Web3.Web3Wallet { /// - Returns: a list of addresses or an error. public func getAccounts() throws -> [EthereumAddress] { guard let keystoreManager = self.web3.provider.attachedKeystoreManager else { - // TODO: add the following error message: Missing `attachedKeystoreManager`. - throw Web3Error.walletError + throw Web3Error.walletError(desc: "Missing `attachedKeystoreManager`. Make sure you set up the `Web3Provider` correctly.") } guard let ethAddresses = keystoreManager.addresses else { - // TODO: add the following error message: Missing attached keystore is empty. - throw Web3Error.walletError + throw Web3Error.walletError(desc: "Attached keystore is empty. `keystoreManager.addresses` returns `nil`.") } return ethAddresses } @@ -26,7 +24,7 @@ extension Web3.Web3Wallet { public func getCoinbase() throws -> EthereumAddress { let addresses = try self.getAccounts() guard addresses.count > 0 else { - throw Web3Error.walletError + throw Web3Error.walletError(desc: "Attached keystore is empty. `keystoreManager.addresses` returns an array with length 0.") } return addresses[0] } @@ -34,13 +32,13 @@ extension Web3.Web3Wallet { public func signTX(transaction: inout CodableTransaction, account: EthereumAddress, password: String ) throws -> Bool { do { guard let keystoreManager = self.web3.provider.attachedKeystoreManager else { - throw Web3Error.walletError + throw Web3Error.walletError(desc: "Missing `attachedKeystoreManager`. Make sure you set up the `Web3Provider` correctly.") } try Web3Signer.signTX(transaction: &transaction, keystore: keystoreManager, account: account, password: password) return true } catch { - if error is AbstractKeystoreError { - throw Web3Error.keystoreError(err: error as! AbstractKeystoreError) + if let error = error as? AbstractKeystoreError { + throw Web3Error.keystoreError(err: error) } throw Web3Error.generalError(err: error) } @@ -48,13 +46,13 @@ extension Web3.Web3Wallet { /// Execute `personal_sign` for given arbitrary message. /// - Parameters: - /// - personalMessage: message. Must be HEX formatted: message -> to 'UTF-8 bytes' -> to hex string! + /// - personalMessage: message. Must be hex formatted: message -> to 'UTF-8 bytes' -> to hex string! /// - account: signer address. /// - password: web3 attached keystore password. /// - Returns: signature for the given message or throws an error. public func signPersonalMessage(_ personalMessage: String, account: EthereumAddress, password: String ) throws -> Data { guard let data = Data.fromHex(personalMessage) else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Given personalMessage must be a valid hex string. Data.fromHex(personalMessage) failed returning `nil`.") } return try self.signPersonalMessage(data, account: account, password: password) } @@ -62,10 +60,11 @@ extension Web3.Web3Wallet { public func signPersonalMessage(_ personalMessage: Data, account: EthereumAddress, password: String ) throws -> Data { do { guard let keystoreManager = self.web3.provider.attachedKeystoreManager else { - throw Web3Error.walletError + throw Web3Error.walletError(desc: "Missing `attachedKeystoreManager`. Make sure you set up the `Web3Provider` correctly.") } guard let data = try Web3Signer.signPersonalMessage(personalMessage, keystore: keystoreManager, account: account, password: password) else { - throw Web3Error.walletError + // FIXME: not so useful description to be honest + throw Web3Error.walletError(desc: "Returned signature is `nil`. Utilities.hashPersonalMessage or SECP256K1.signForRecovery failed.") } return data } catch { diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index 10fd972cb..4be42f4cb 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -39,7 +39,7 @@ public class WriteOperation: ReadOperation { } catch { throw Web3Error.inputError(desc: "Failed to locally sign a transaction. \(error.localizedDescription)") } - guard let transactionData = transaction.encode(for: .transaction) else { throw Web3Error.dataError } + guard let transactionData = transaction.encode(for: .transaction) else { throw Web3Error.transactionSerializationError(desc: "Failed to encode transaction. Check transaction data and envelope type. EncodeType used is `.transaction`.") } return try await web3.eth.send(raw: transactionData) } } diff --git a/Sources/web3swift/Utils/ENS/ENS.swift b/Sources/web3swift/Utils/ENS/ENS.swift index b561e8af9..84a640a29 100755 --- a/Sources/web3swift/Utils/ENS/ENS.swift +++ b/Sources/web3swift/Utils/ENS/ENS.swift @@ -74,10 +74,6 @@ public class ENS { self.reverseRegistrar = reverseRegistrar } - lazy var defaultTransaction: CodableTransaction = { - return CodableTransaction.emptyTransaction - }() - // MARK: - Convenience public resolver methods public func getAddress(forNode node: String) async throws -> EthereumAddress { guard let resolver = try? await self.registry.getResolver(forDomain: node) else { @@ -106,7 +102,7 @@ public class ENS { guard isAddrSupports else { throw Web3Error.processingError(desc: "Address isn't supported") } - var transaction = transaction ?? defaultTransaction + var transaction = transaction ?? CodableTransaction.emptyTransaction transaction.to = resolver.resolverContractAddress guard let result = try? await resolver.setAddress(forNode: node, address: address, transaction: transaction, password: password) else { throw Web3Error.processingError(desc: "Can't get result") @@ -141,7 +137,7 @@ public class ENS { guard isNameSupports else { throw Web3Error.processingError(desc: "Name isn't supported") } - var options = transaction ?? defaultTransaction + var options = transaction ?? CodableTransaction.emptyTransaction options.to = resolver.resolverContractAddress guard let result = try? await resolver.setCanonicalName(forNode: node, name: name, transaction: options, password: password) else { throw Web3Error.processingError(desc: "Can't get result") @@ -177,7 +173,7 @@ public class ENS { guard isContentSupports else { throw Web3Error.processingError(desc: "Content isn't supported") } - var options = transaction ?? defaultTransaction + var options = transaction ?? CodableTransaction.emptyTransaction options.to = resolver.resolverContractAddress guard let result = try? await resolver.setContentHash(forNode: node, hash: hash, transaction: options, password: password) else { throw Web3Error.processingError(desc: "Can't get result") @@ -212,7 +208,7 @@ public class ENS { guard isABISupports else { throw Web3Error.processingError(desc: "ABI isn't supported") } - var options = transaction ?? defaultTransaction + var options = transaction ?? CodableTransaction.emptyTransaction options.to = resolver.resolverContractAddress guard let result = try? await resolver.setContractABI(forNode: node, contentType: contentType, data: data, transaction: options, password: password) else { throw Web3Error.processingError(desc: "Can't get result") @@ -247,7 +243,7 @@ public class ENS { guard isPKSupports else { throw Web3Error.processingError(desc: "Public Key isn't supported") } - var options = transaction ?? defaultTransaction + var options = transaction ?? CodableTransaction.emptyTransaction options.to = resolver.resolverContractAddress guard let result = try? await resolver.setPublicKey(forNode: node, publicKey: publicKey, transaction: options, password: password) else { throw Web3Error.processingError(desc: "Can't get result") @@ -282,7 +278,7 @@ public class ENS { guard isTextSupports else { throw Web3Error.processingError(desc: "Text isn't supported") } - var options = transaction ?? defaultTransaction + var options = transaction ?? CodableTransaction.emptyTransaction options.to = resolver.resolverContractAddress guard let result = try? await resolver.setTextData(forNode: node, key: key, value: value, transaction: options, password: password) else { throw Web3Error.processingError(desc: "Can't get result") diff --git a/Sources/web3swift/Utils/ENS/ENSBaseRegistrar.swift b/Sources/web3swift/Utils/ENS/ENSBaseRegistrar.swift index 6c2a7614c..63bcd7cf3 100644 --- a/Sources/web3swift/Utils/ENS/ENSBaseRegistrar.swift +++ b/Sources/web3swift/Utils/ENS/ENSBaseRegistrar.swift @@ -12,10 +12,6 @@ import Web3Core // FIXME: Rewrite this to CodableTransaction public extension ENS { class BaseRegistrar: ERC721 { - lazy var defaultTransaction: CodableTransaction = { - return CodableTransaction.emptyTransaction - }() - public init(web3: Web3, address: EthereumAddress) { super.init(web3: web3, provider: web3.provider, address: address) guard let contract = self.web3.contract(Web3.Utils.baseRegistrarABI, at: self.address, abiVersion: 2) else { @@ -34,50 +30,30 @@ public extension ENS { @available(*, message: "Available for only owner") public func addController(from: EthereumAddress, controllerAddress: EthereumAddress) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("addController", parameters: [controllerAddress]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("addController", parameters: [controllerAddress]) } @available(*, message: "Available for only owner") public func removeController(from: EthereumAddress, controllerAddress: EthereumAddress) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("removeController", parameters: [controllerAddress]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("removeController", parameters: [controllerAddress]) } @available(*, message: "Available for only owner") public func setResolver(from: EthereumAddress, resolverAddress: EthereumAddress) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("setResolver", parameters: [resolverAddress]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("setResolver", parameters: [resolverAddress]) } public func getNameExpirity(name: BigUInt) async throws -> BigUInt { - guard let transaction = self.contract.createReadOperation("nameExpires", parameters: [name]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let expirity = result["0"] as? BigUInt else { throw Web3Error.processingError(desc: "Can't get answer") } - return expirity + try await contract.callReadOnlyFunction("nameExpires", parameters: [name]) } @available(*, message: "This function should not be used to check if a name can be registered by a user. To check if a name can be registered by a user, check name availability via the controller") public func isNameAvailable(name: BigUInt) async throws -> Bool { - guard let transaction = self.contract.createReadOperation("available", parameters: [name]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let available = result["0"] as? Bool else { throw Web3Error.processingError(desc: "Can't get answer") } - return available + try await contract.callReadOnlyFunction("available", parameters: [name]) } public func reclaim(from: EthereumAddress, record: BigUInt) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("reclaim", parameters: [record]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("reclaim", parameters: [record]) } } diff --git a/Sources/web3swift/Utils/ENS/ENSRegistry.swift b/Sources/web3swift/Utils/ENS/ENSRegistry.swift index e3ae95a8a..6d3b07967 100644 --- a/Sources/web3swift/Utils/ENS/ENSRegistry.swift +++ b/Sources/web3swift/Utils/ENS/ENSRegistry.swift @@ -11,125 +11,80 @@ import Web3Core public extension ENS { class Registry { + + internal static let ethereumMainnetENSRegistryAddress: EthereumAddress? = EthereumAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e") + internal static let goerliTestnetENSRegistryAddress: EthereumAddress? = EthereumAddress("0x112234455c3a32fd11230c42e7bccd4a84e02010") + public let web3: Web3 - public let registryContractAddress: EthereumAddress? + public let registryContractAddress: EthereumAddress public init?(web3: Web3) { self.web3 = web3 switch web3.provider.network { - case .Mainnet?: - registryContractAddress = EthereumAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e") - case .Rinkeby?: - registryContractAddress = EthereumAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e") - case .Ropsten?: - registryContractAddress = EthereumAddress("0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e") + case .Mainnet?, .Rinkeby?, .Ropsten?: + registryContractAddress = Registry.ethereumMainnetENSRegistryAddress! default: let url = web3.provider.url.absoluteString if url.contains("https://rpc.goerli.mudit.blog") || url.contains("http://goerli.blockscout.com") || url.contains("http://goerli.prylabs.net") || url.contains("https://rpc.slock.it/goerli") { - registryContractAddress = EthereumAddress("0x112234455c3a32fd11230c42e7bccd4a84e02010") + registryContractAddress = Registry.goerliTestnetENSRegistryAddress! } return nil } } - lazy var defaultTransaction: CodableTransaction = { - return CodableTransaction.emptyTransaction - }() - lazy var registryContract: Web3.Contract = { let contract = self.web3.contract(Web3.Utils.ensRegistryABI, at: self.registryContractAddress, abiVersion: 2) precondition(contract != nil) return contract! }() - public func getOwner(node: String) async throws -> EthereumAddress { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} + private func nameHash(_ value: String) throws -> Data { + guard let nameHash = NameHash.nameHash(value) else { + throw Web3Error.processingError(desc: "NameHash.nameHash(\(value) failed. Please check the given value.") + } + return nameHash + } - guard let transaction = self.registryContract.createReadOperation("owner", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } + private func writeToChain(_ methodName: String, parameters: [Any], password: String) async throws -> TransactionSendingResult { + let writeOperation = try registryContract.createWriteFunctionCall(methodName, parameters: parameters) + writeOperation.transaction.to = registryContractAddress + return try await writeOperation.writeToChain(password: password) + } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let address = result["0"] as? EthereumAddress else {throw Web3Error.processingError(desc: "No address in result")} - return address + public func getOwner(node: String) async throws -> EthereumAddress { + try await registryContract.callReadOnlyFunction("owner", parameters: [try nameHash(node)]) } public func getResolver(forDomain domain: String) async throws -> Resolver { - guard let nameHash = NameHash.nameHash(domain) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - - guard let transaction = self.registryContract.createReadOperation("resolver", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let resolverAddress = result["0"] as? EthereumAddress else {throw Web3Error.processingError(desc: "No address in result")} + let resolverAddress: EthereumAddress = try await registryContract.callReadOnlyFunction("resolver", parameters: [try nameHash(domain)]) return Resolver(web3: self.web3, resolverContractAddress: resolverAddress) } public func getTTL(node: String) async throws -> BigUInt { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - - guard let transaction = self.registryContract.createReadOperation("ttl", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let ans = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "No answer in result")} - return ans + try await registryContract.callReadOnlyFunction("ttl", parameters: [try nameHash(node)]) } // FIXME: Rewrite this to CodableTransaction public func setOwner(node: String, owner: EthereumAddress, transaction: CodableTransaction?, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - if let contractAddress = self.registryContractAddress { - transaction.to = contractAddress - } - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - - guard let transaction = self.registryContract.createWriteOperation("setOwner", parameters: [nameHash, owner]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setOwner", parameters: [try nameHash(node), owner], password: password) } // FIXME: Rewrite this to CodableTransaction public func setSubnodeOwner(node: String, label: String, owner: EthereumAddress, transaction: CodableTransaction?, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - if let contractAddress = self.registryContractAddress { - transaction.to = contractAddress - } - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let labelHash = NameHash.nameHash(label) else {throw Web3Error.processingError(desc: "Failed to get label hash")} - - guard let transaction = self.registryContract.createWriteOperation("setSubnodeOwner", parameters: [nameHash, labelHash, owner]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setSubnodeOwner", parameters: [try nameHash(node), try nameHash(label), owner], password: password) } // FIXME: Rewrite this to CodableTransaction public func setResolver(node: String, resolver: EthereumAddress, transaction: CodableTransaction?, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - if let contractAddress = self.registryContractAddress { - transaction.to = contractAddress - } - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - - guard let transaction = self.registryContract.createWriteOperation("setResolver", parameters: [nameHash, resolver]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setResolver", parameters: [try nameHash(node), resolver], password: password) } // FIXME: Rewrite this to CodableTransaction public func setTTL(node: String, ttl: BigUInt, transaction: CodableTransaction?, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - if let contractAddress = self.registryContractAddress { - transaction.to = contractAddress - } - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - - guard let transaction = self.registryContract.createWriteOperation("setTTL", parameters: [nameHash, ttl]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setTTL", parameters: [try nameHash(node), ttl], password: password) } } } diff --git a/Sources/web3swift/Utils/ENS/ENSResolver.swift b/Sources/web3swift/Utils/ENS/ENSResolver.swift index d0fc62bc3..b10e1060f 100755 --- a/Sources/web3swift/Utils/ENS/ENSResolver.swift +++ b/Sources/web3swift/Utils/ENS/ENSResolver.swift @@ -51,134 +51,105 @@ public extension ENS { return contract! }() - lazy var defaultTransaction: CodableTransaction = { - return CodableTransaction.emptyTransaction - }() - public init(web3: Web3, resolverContractAddress: EthereumAddress) { self.web3 = web3 self.resolverContractAddress = resolverContractAddress } - public func supportsInterface(interfaceID: Data) async throws -> Bool { - guard let supports = try? await supportsInterface(interfaceID: interfaceID.toHexString()) else { - throw Web3Error.processingError(desc: "Can't get answer") + private func nameHash(_ value: String) throws -> Data { + guard let nameHash = NameHash.nameHash(value) else { + throw Web3Error.processingError(desc: "NameHash.nameHash(\(value) failed. Please check the given value.") } - return supports + return nameHash + } + + private func writeToChain(_ methodName: String, parameters: [Any], password: String) async throws -> TransactionSendingResult { + let writeOperation = try resolverContract.createWriteFunctionCall(methodName, parameters: parameters) + writeOperation.transaction.to = resolverContractAddress + return try await writeOperation.writeToChain(password: password) + } + + public func supportsInterface(interfaceID: Data) async throws -> Bool { + try await supportsInterface(interfaceID: interfaceID.toHexString()) } public func supportsInterface(interfaceID: InterfaceName) async throws -> Bool { - guard let supports = try? await supportsInterface(interfaceID: interfaceID.hash) else { - throw Web3Error.processingError(desc: "Can't get answer") - } - return supports + try await supportsInterface(interfaceID: interfaceID.hash) } public func supportsInterface(interfaceID: String) async throws -> Bool { - guard let transaction = self.resolverContract.createReadOperation("supportsInterface", parameters: [interfaceID]) else { - throw Web3Error.transactionSerializationError - } - guard let result = try? await transaction.call() else { - throw Web3Error.processingError(desc: "Can't call transaction") - } - guard let supports = result["0"] as? Bool else { - throw Web3Error.processingError(desc: "Can't get answer") - } - return supports + try await resolverContract.callReadOnlyFunction("supportsInterface", parameters: [interfaceID]) } public func interfaceImplementer(forNode node: String, interfaceID: String) async throws -> EthereumAddress { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("interfaceImplementer", parameters: [nameHash, interfaceID]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let address = result["0"] as? EthereumAddress else {throw Web3Error.processingError(desc: "Can't get address")} - return address + try await resolverContract.callReadOnlyFunction("interfaceImplementer", parameters: [try nameHash(node), interfaceID]) } public func getAddress(forNode node: String) async throws -> EthereumAddress { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("addr", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let address = result["0"] as? EthereumAddress else {throw Web3Error.processingError(desc: "Can't get address")} - return address + try await resolverContract.callReadOnlyFunction("addr", parameters: [try nameHash(node)]) } // FIXME: Rewrite this to CodableTransaction @available(*, message: "Available for only owner") public func setAddress(forNode node: String, address: EthereumAddress, transaction: CodableTransaction? = nil, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - transaction.to = self.resolverContractAddress - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createWriteOperation("setAddr", parameters: [nameHash, address]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setAddr", parameters: [try nameHash(node), address], password: password) } public func getCanonicalName(forNode node: String) async throws -> String { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("name", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let name = result["0"] as? String else {throw Web3Error.processingError(desc: "Can't get name")} - return name + try await resolverContract.callReadOnlyFunction("name", parameters: [try nameHash(node)]) } // FIXME: Rewrite this to CodableTransaction @available(*, message: "Available for only owner") func setCanonicalName(forNode node: String, name: String, transaction: CodableTransaction? = nil, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - transaction.to = self.resolverContractAddress - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createWriteOperation("setName", parameters: [nameHash, name]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setName", parameters: [try nameHash(node), name], password: password) } func getContentHash(forNode node: String) async throws -> Data { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("contenthash", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let content = result["0"] as? Data else {throw Web3Error.processingError(desc: "Can't get content")} - return content + try await resolverContract.callReadOnlyFunction("contenthash", parameters: [try nameHash(node)]) } // FIXME: Rewrite this to CodableTransaction @available(*, message: "Available for only owner") func setContentHash(forNode node: String, hash: String, transaction: CodableTransaction? = nil, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - transaction.to = self.resolverContractAddress - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createWriteOperation("setContenthash", parameters: [nameHash, hash]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.writeToChain(password: password) - else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setContenthash", parameters: [try nameHash(node), hash], password: password) } public func getContractABI(forNode node: String, contentType: ENS.Resolver.ContentType) async throws -> (BigUInt, Data) { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("ABI", parameters: [nameHash, contentType.rawValue]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let encoding = result["0"] as? BigUInt else {throw Web3Error.processingError(desc: "Can't get encoding")} - guard let data = result["1"] as? Data else {throw Web3Error.processingError(desc: "Can't get data")} + let parameters: [Any] = [try nameHash(node), contentType.rawValue] + guard let transaction = self.resolverContract.createReadOperation("ABI", parameters: parameters) else { + throw Web3Error.transactionSerializationError(desc: "Failed to encode `ABI` function call with given parameters \(String(describing: parameters)). Make sure contract contains `ABI` function ABI and the order of given parameters is correct.") + } + + let result = try await transaction.call() + + guard let encoding = result["0"] as? BigUInt else { + throw Web3Error.processingError(desc: "Response value for key \"0\" cannot be cast to type BigUInt. Response is: \(String(describing: result["0"])).") + } + guard let data = result["1"] as? Data else { + throw Web3Error.processingError(desc: "Response value for key \"1\" cannot be cast to type Data. Response is: \(String(describing: result["1"])).") + } return (encoding, data) } // FIXME: Rewrite this to CodableTransaction @available(*, message: "Available for only owner") func setContractABI(forNode node: String, contentType: ENS.Resolver.ContentType, data: Data, transaction: CodableTransaction? = nil, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - transaction.to = self.resolverContractAddress - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createWriteOperation("setABI", parameters: [nameHash, contentType.rawValue, data]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setABI", parameters: [try nameHash(node), contentType.rawValue, data], password: password) } public func getPublicKey(forNode node: String) async throws -> PublicKey { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("pubkey", parameters: [nameHash]) else { throw Web3Error.transactionSerializationError } + let parameters: [Any] = [try nameHash(node)] + guard let transaction = self.resolverContract.createReadOperation("pubkey", parameters: parameters) else { + throw Web3Error.transactionSerializationError(desc: "Failed to encode `pubkey` function call with given parameters \(String(describing: parameters)). Make sure contract contains `pubkey` function ABI and the order of given parameters is correct.") + } guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let x = result["x"] as? Data else {throw Web3Error.processingError(desc: "Can't get x")} - guard let y = result["y"] as? Data else {throw Web3Error.processingError(desc: "Can't get y")} + guard let x = result["x"] as? Data else { + throw Web3Error.processingError(desc: "Response value for key \"x\" cannot be cast to type Data. Response is: \(String(describing: result["x"])).") + } + guard let y = result["y"] as? Data else { + throw Web3Error.processingError(desc: "Response value for key \"y\" cannot be cast to type Data. Response is: \(String(describing: result["y"])).") + } let pubkey = PublicKey(x: "0x" + x.toHexString(), y: "0x" + y.toHexString()) return pubkey } @@ -186,32 +157,18 @@ public extension ENS { // FIXME: Rewrite this to CodableTransaction @available(*, message: "Available for only owner") public func setPublicKey(forNode node: String, publicKey: PublicKey, transaction: CodableTransaction? = nil, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - transaction.to = self.resolverContractAddress let pubkeyWithoutPrefix = publicKey.getComponentsWithoutPrefix() - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createWriteOperation("setPubkey", parameters: [nameHash, pubkeyWithoutPrefix.x, pubkeyWithoutPrefix.y]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + return try await writeToChain("setPubkey", parameters: [try nameHash(node), pubkeyWithoutPrefix.x, pubkeyWithoutPrefix.y], password: password) } public func getTextData(forNode node: String, key: String) async throws -> String { - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createReadOperation("text", parameters: [nameHash, key]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else {throw Web3Error.processingError(desc: "Can't call transaction")} - guard let text = result["0"] as? String else {throw Web3Error.processingError(desc: "Can't get text")} - return text + try await resolverContract.callReadOnlyFunction("text", parameters: [try nameHash(node), key]) } // FIXME: Rewrite this to CodableTransaction @available(*, message: "Available for only owner") public func setTextData(forNode node: String, key: String, value: String, transaction: CodableTransaction? = nil, password: String) async throws -> TransactionSendingResult { - var transaction = transaction ?? defaultTransaction - transaction.to = self.resolverContractAddress - guard let nameHash = NameHash.nameHash(node) else {throw Web3Error.processingError(desc: "Failed to get name hash")} - guard let transaction = self.resolverContract.createWriteOperation("setText", parameters: [nameHash, key, value]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.writeToChain(password: password) else {throw Web3Error.processingError(desc: "Can't send transaction")} - return result + try await writeToChain("setText", parameters: [try nameHash(node), key, value], password: password) } } } diff --git a/Sources/web3swift/Utils/ENS/ENSReverseRegistrar.swift b/Sources/web3swift/Utils/ENS/ENSReverseRegistrar.swift index 2d9add8c1..b11ba9aa9 100644 --- a/Sources/web3swift/Utils/ENS/ENSReverseRegistrar.swift +++ b/Sources/web3swift/Utils/ENS/ENSReverseRegistrar.swift @@ -22,50 +22,29 @@ public extension ENS { // swiftlint:enable force_unwrapping }() - lazy var defaultTransaction: CodableTransaction = { - return CodableTransaction.emptyTransaction - }() - public init(web3: Web3, address: EthereumAddress) { self.web3 = web3 self.address = address } public func claimAddress(from: EthereumAddress, owner: EthereumAddress) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("claim", parameters: [owner]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("claim", parameters: [owner]) } public func claimAddressWithResolver(from: EthereumAddress, owner: EthereumAddress, resolver: EthereumAddress) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("claimWithResolver", parameters: [owner, resolver]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("claimWithResolver", parameters: [owner, resolver]) } public func setName(from: EthereumAddress, name: String) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("setName", parameters: [name]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("setName", parameters: [name]) } public func getReverseRecordName(address: EthereumAddress) async throws -> Data { - guard let transaction = self.contract.createReadOperation("node", parameters: [address]) else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let name = result["0"] as? Data else { throw Web3Error.processingError(desc: "Can't get answer") } - return name + try await contract.callReadOnlyFunction("node", parameters: [address]) } public func getDefaultResolver() async throws -> EthereumAddress { - guard let transaction = self.contract.createReadOperation("defaultResolver") else { throw Web3Error.transactionSerializationError } - - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let address = result["0"] as? EthereumAddress else { throw Web3Error.processingError(desc: "Can't get answer") } - return address + try await contract.callReadOnlyFunction("defaultResolver") } } } diff --git a/Sources/web3swift/Utils/ENS/ETHRegistrarController.swift b/Sources/web3swift/Utils/ENS/ETHRegistrarController.swift index 7d41e20e1..4916026f6 100644 --- a/Sources/web3swift/Utils/ENS/ETHRegistrarController.swift +++ b/Sources/web3swift/Utils/ENS/ETHRegistrarController.swift @@ -22,74 +22,44 @@ public extension ENS { // swiftlint:enable force_unwrapping }() - lazy var defaultTransaction: CodableTransaction = { - return CodableTransaction.emptyTransaction - }() - public init(web3: Web3, address: EthereumAddress) { self.web3 = web3 self.address = address } public func getRentPrice(name: String, duration: UInt) async throws -> BigUInt { - guard let transaction = self.contract.createReadOperation("rentPrice", parameters: [name, duration]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let price = result["0"] as? BigUInt else { throw Web3Error.processingError(desc: "Can't get answer") } - return price + try await contract.callReadOnlyFunction("rentPrice", parameters: [name, duration]) } public func checkNameValidity(name: String) async throws -> Bool { - guard let transaction = self.contract.createReadOperation("valid", parameters: [name]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let valid = result["0"] as? Bool else { throw Web3Error.processingError(desc: "Can't get answer") } - return valid + try await contract.callReadOnlyFunction("valid", parameters: [name]) } public func isNameAvailable(name: String) async throws -> Bool { - guard let transaction = self.contract.createReadOperation("available", parameters: [name]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let available = result["0"] as? Bool else { throw Web3Error.processingError(desc: "Can't get answer") } - return available + try await contract.callReadOnlyFunction("available", parameters: [name]) } public func calculateCommitmentHash(name: String, owner: EthereumAddress, secret: String) async throws -> Data { - guard let transaction = self.contract.createReadOperation("makeCommitment", parameters: [name, owner.address, secret]) else { throw Web3Error.transactionSerializationError } - guard let result = try? await transaction.call() else { throw Web3Error.processingError(desc: "Can't call transaction") } - guard let hash = result["0"] as? Data else { throw Web3Error.processingError(desc: "Can't get answer") } - return hash + try await contract.callReadOnlyFunction("makeCommitment", parameters: [name, owner, secret]) } public func sumbitCommitment(from: EthereumAddress, commitment: Data) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("commit", parameters: [commitment]) else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("commit", parameters: [commitment]) } public func registerName(from: EthereumAddress, name: String, owner: EthereumAddress, duration: UInt, secret: String, price: String) throws -> WriteOperation { guard let amount = Utilities.parseToBigUInt(price, units: .ether) else { throw Web3Error.inputError(desc: "Wrong price: no way for parsing to ether units") } - defaultTransaction.value = amount - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("register", parameters: [name, owner.address, duration, secret]) else { throw Web3Error.transactionSerializationError } - return transaction + return try contract.createWriteFunctionCall("register", parameters: [name, owner.address, duration, secret]) } public func extendNameRegistration(from: EthereumAddress, name: String, duration: UInt32, price: String) throws -> WriteOperation { guard let amount = Utilities.parseToBigUInt(price, units: .ether) else { throw Web3Error.inputError(desc: "Wrong price: no way for parsing to ether units") } - defaultTransaction.value = amount - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("renew", parameters: [name, duration]) else { throw Web3Error.transactionSerializationError } - return transaction + return try contract.createWriteFunctionCall("renew", parameters: [name, duration]) } @available(*, message: "Available for only owner") public func withdraw(from: EthereumAddress) throws -> WriteOperation { - defaultTransaction.from = from - defaultTransaction.to = self.address - guard let transaction = self.contract.createWriteOperation("withdraw") else { throw Web3Error.transactionSerializationError } - return transaction + try contract.createWriteFunctionCall("withdraw") } } } diff --git a/Sources/web3swift/Utils/ENS/NameHash.swift b/Sources/web3swift/Utils/ENS/NameHash.swift index 9c6654a58..3b95f5a0e 100755 --- a/Sources/web3swift/Utils/ENS/NameHash.swift +++ b/Sources/web3swift/Utils/ENS/NameHash.swift @@ -7,15 +7,14 @@ import Foundation import CryptoSwift public struct NameHash { - public static func normalizeDomainName(_ domain: String) -> String? { + public static func normalizeDomainName(_ domain: String) -> String { // TODO use ICU4C library later for domain name normalization, although f**k it for now, it's few megabytes large piece let normalized = domain.lowercased() return normalized } public static func nameHash(_ domain: String) -> Data? { - guard let normalized = NameHash.normalizeDomainName(domain) else { return nil } - return namehash(normalized) + namehash(NameHash.normalizeDomainName(domain)) } static func namehash(_ name: String) -> Data? { diff --git a/Sources/web3swift/Web3/Web3+Contract.swift b/Sources/web3swift/Web3/Web3+Contract.swift index d5fc82da1..a6e057f5a 100755 --- a/Sources/web3swift/Web3/Web3+Contract.swift +++ b/Sources/web3swift/Web3/Web3+Contract.swift @@ -113,6 +113,29 @@ extension Web3 { return .init(transaction: transaction, web3: web3, contract: contract, method: method) } + // FIXME: Reduces code duplication. + // FIXME: this function is temporary just for convenience of checking for errors + // FIXME: and reporting useful information back to the developers. + // FIXME: this function will be removed later as continue refactoring + internal func callReadOnlyFunction(_ method: String, parameters: [Any] = []) async throws -> T { + guard let transaction = createReadOperation(method, parameters: parameters) else { + throw Web3Error.transactionSerializationError(desc: "Failed to encode `\(method)` function call with given parameters \(String(describing: parameters)). Make sure contract contains `\(method)` function ABI and the order of given parameters is correct.") + } + let result = try await transaction.call() + guard let returnValue = result["0"] as? T else { + throw Web3Error.processingError(desc: "Response value for key \"0\" cannot be cast to type \(T.self). Response is: \(String(describing: result)).") + } + return returnValue + } + + // FIXME: See callReadOnlyFunction description. + internal func createWriteFunctionCall(_ method: String, parameters: [Any] = []) throws -> WriteOperation { + guard let transaction = createWriteOperation(method, parameters: parameters) else { + throw Web3Error.transactionSerializationError(desc: "Failed to encode `\(method)` function call with given parameters \(String(describing: parameters)). Make sure contract contains `\(method)` function ABI and the order of given parameters is correct.") + } + return transaction + } + /// Combines `createReadOperation` & `callContractMethod` @discardableResult public func callStatic(_ method: String, parameters: [Any]) async throws -> [String: Any] { diff --git a/Sources/web3swift/Web3/Web3+Personal.swift b/Sources/web3swift/Web3/Web3+Personal.swift index 42e21f6f6..e781390a3 100755 --- a/Sources/web3swift/Web3/Web3+Personal.swift +++ b/Sources/web3swift/Web3/Web3+Personal.swift @@ -50,7 +50,7 @@ extension Web3.Personal { if let address = recoverAddress(message: personalMessage, signature: signature) { return address } - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to ecrecover an address from given personalMessage and signature. Personal message bytes as hex string = \(personalMessage.toHexString()); signature = \(signature.toHexString()).") } /// Recovers a signer of some hash. @@ -67,6 +67,6 @@ extension Web3.Personal { if let address = recoverAddress(hash: hash, signature: signature) { return address } - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to ecrecover an address from given hash and signature. Hash = \(hash.toHexString()); signature = \(signature.toHexString()).") } } diff --git a/Sources/web3swift/Web3/Web3+Signing.swift b/Sources/web3swift/Web3/Web3+Signing.swift index 46fe01ce5..a000291cd 100755 --- a/Sources/web3swift/Web3/Web3+Signing.swift +++ b/Sources/web3swift/Web3/Web3+Signing.swift @@ -54,7 +54,7 @@ public struct Web3Signer { account: account, password: password ?? "") else { - throw Web3Error.dataError + throw Web3Error.dataError(desc: "Failed to sign personal message. Hashing or signing failed and returned `nil`.") } return signature } diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift index 3e7f077d8..8a8be7d4b 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -55,7 +55,8 @@ final class ABIDecoderSliceTests: XCTestCase { let decodedData = try multiCall2Contract.decodeReturnData("aggregate", data: data) guard let returnData = decodedData["returnData"] as? [Data] else { - throw Web3Error.dataError + XCTFail("Failed to cast 'returnData' to [Data].") + return } XCTAssertEqual(returnData.count, 3) @@ -75,7 +76,8 @@ final class ABIDecoderSliceTests: XCTestCase { let decodedData = try contract.decodeReturnData("tryAggregate", data: data) guard let returnData = decodedData["returnData"] as? [[Any]] else { - throw Web3Error.dataError + XCTFail("Failed to cast 'returnData' to [[Any]].") + return } var resultArray = [BigUInt]() for i in 0..<2 { diff --git a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift index d0aa1b9d8..a9db54a41 100644 --- a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift +++ b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift @@ -19,6 +19,7 @@ class ABIElementErrorDecodingTest: XCTestCase { outputs: [], constant: false, payable: false) + private let emptyEthError = ABI.Element.EthError(name: "none") private let oneOutputFunction = ABI.Element.Function(name: "any", inputs: [], outputs: [.init(name: "", type: .bool)], @@ -62,7 +63,7 @@ class ABIElementErrorDecodingTest: XCTestCase { /// If a function that has no outputs attempts to decode empty `revert` or `require` must return `nil` /// because we don't know just based on the output if the call was successful or reverted. func testDecodeEmptyErrorOnNoOutputFunction() { - XCTAssertTrue(emptyFunction.decodeErrorResponse(Data()) == nil) + XCTAssertTrue(emptyEthError.decodeEthError(Data()) == nil) } /// `require(expression)` and `revert()` without a message return 0 bytes, diff --git a/Tests/web3swiftTests/localTests/ABIEncoderTest.swift b/Tests/web3swiftTests/localTests/ABIEncoderTest.swift index 2e13e4334..255057b14 100644 --- a/Tests/web3swiftTests/localTests/ABIEncoderTest.swift +++ b/Tests/web3swiftTests/localTests/ABIEncoderTest.swift @@ -122,7 +122,7 @@ class ABIEncoderTest: XCTestCase { constant: false, payable: false) let encodedFunction = functionWithNoInput.encodeParameters([]) - XCTAssertTrue(functionWithNoInput.methodEncoding == encodedFunction) + XCTAssertTrue(functionWithNoInput.selectorEncoded == encodedFunction) XCTAssertTrue("0xe16b4a9b" == encodedFunction?.toHexString().addHexPrefix().lowercased()) } diff --git a/Tests/web3swiftTests/localTests/APIRequestTests.swift b/Tests/web3swiftTests/localTests/APIRequestTests.swift new file mode 100644 index 000000000..3b62ad4f6 --- /dev/null +++ b/Tests/web3swiftTests/localTests/APIRequestTests.swift @@ -0,0 +1,22 @@ +// +// APIRequestTests.swift +// +// Created by JeneaVranceanu on 09.01.2024. +// + +import Foundation +import XCTest +@testable import web3swift +@testable import Web3Core + +class APIRequestTests: XCTestCase { + + func testLiteralTypeParsingErrors() throws { + do { + let _: APIResponse? = try APIRequest.initalizeLiteralTypeApiResponse(Data()) + } catch (let error) { + XCTAssertTrue((error as! Web3Error).errorDescription!.starts(with: "Failed to decode received data as `APIResponse`")) + } + } + +} diff --git a/Tests/web3swiftTests/remoteTests/ENSTests.swift b/Tests/web3swiftTests/remoteTests/ENSTests.swift index a21c5f848..55179652f 100755 --- a/Tests/web3swiftTests/remoteTests/ENSTests.swift +++ b/Tests/web3swiftTests/remoteTests/ENSTests.swift @@ -4,8 +4,7 @@ // import XCTest -import Web3Core - +@testable import Web3Core @testable import web3swift // MARK: Works only with network connection @@ -103,4 +102,9 @@ class ENSTests: XCTestCase { XCTAssert(pubkey?.x == "0x0000000000000000000000000000000000000000000000000000000000000000") XCTAssert(pubkey?.y == "0x0000000000000000000000000000000000000000000000000000000000000000") } + + func testRegistryAddressesNotNil() async { + XCTAssertNotNil(ENS.Registry.ethereumMainnetENSRegistryAddress) + XCTAssertNotNil(ENS.Registry.goerliTestnetENSRegistryAddress) + } }