Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore: error description+refactoring #841

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/Web3Core/Contract/ContractProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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...])
Expand All @@ -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)

Expand Down
18 changes: 10 additions & 8 deletions Sources/Web3Core/EthereumABI/ABIElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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?

Expand Down
2 changes: 1 addition & 1 deletion Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Web3Core/EthereumAddress/EthereumAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
34 changes: 23 additions & 11 deletions Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>.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<Result>.self, from: data)
return try initalizeLiteralTypeApiResponse(data) ?? JSONDecoder().decode(APIResponse<Result>.self, from: data)
}

public static func send(uRLRequest: URLRequest, with session: URLSession) async throws -> Data {
Expand Down Expand Up @@ -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<Result>(_ data: Data) throws -> APIResponse<Result>? {
guard let LiteralType = Result.self as? LiteralInitiableFromString.Type else {
return nil
}
guard let responseAsString = try? JSONDecoder().decode(APIResponse<String>.self, from: data) else {
throw Web3Error.dataError(desc: "Failed to decode received data as `APIResponse<String>`. 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd purpose to switch all such string functions as a properties. e.g. apiKey.trimed, function.selector.hexPrefixAdded.lowercased. I mean to add such as a separate issue for further work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I do not mind but what is the benefit or reason for it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This way it would better fits with clean code rules. Actually this way it wouldn't violates the rule of in-depth abstraction layers mixing as it is with chain function calls.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To be handled in a separate PR.

}

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)
Expand Down
4 changes: 3 additions & 1 deletion Sources/Web3Core/Structure/Block/Block.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/Web3Core/Structure/EventLog/EventLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Web3Core/Structure/TxPool/TxPoolContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion Sources/Web3Core/Transaction/CodableTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions Sources/Web3Core/Transaction/Envelope/EIP1559Envelope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
20 changes: 13 additions & 7 deletions Sources/Web3Core/Transaction/Envelope/EIP2930Envelope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/Web3Core/Transaction/Envelope/EnvelopeFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 5 additions & 3 deletions Sources/Web3Core/Transaction/Envelope/LegacyEnvelope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/Web3Core/Utility/Data+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ public extension Data {
}

func toArray<T>(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<T>.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..<self.count { // compare full length
difference |= self[i] ^ rhs[i] // constant time
Expand Down
Loading
Loading