diff --git a/README.md b/README.md index e9a55bb90..490c6ac30 100755 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ - [x] ✅**Literally following the standards** (BIP, EIP, etc): - [x] **[BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) (HD Wallets), [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) (Seed phrases), [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) (Key generation prefixes)** - [x] **[EIP-20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)** (Standard interface for tokens - ERC-20), **[EIP-67](https://github.com/ethereum/EIPs/issues/67)** (Standard URI scheme), **[EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)** (Replay attacks protection), **[EIP-2718](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md)** (Typed Transaction Envelope), **[EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md)** (Gas Fee market change) - - [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/web3swift-team/web3swift/blob/master/Documentation/))*: EIP-681, EIP-721, EIP-165, EIP-777, EIP-820, EIP-888, EIP-1400, EIP-1410, EIP-1594, EIP-1643, EIP-1644, EIP-1633, EIP-721, EIP-1155, EIP-1376, ST-20 + - [x] **And many others** *(For details about this EIP's look at [Documentation page](https://github.com/web3swift-team/web3swift/blob/master/Documentation/))*: EIP-165, EIP-681, EIP-721, EIP-777, EIP-820, EIP-888, EIP-1155, EIP-1376, EIP-1400, EIP-1410, EIP-1594, EIP-1633, EIP-1643, EIP-1644, EIP-4361 ([SIWE](https://eips.ethereum.org/EIPS/eip-4361)), ST-20 - [x] **RLP encoding** - [x] Base58 encoding scheme - [x] Formatting to and from Ethereum Units diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 43a4c1543..5a2ab8863 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -143,8 +143,12 @@ public protocol ContractProtocol { /// - name with arguments:`myFunction(uint256)`. /// - method signature (with or without `0x` prefix, case insensitive): `0xFFffFFff`; /// - data: non empty bytes to decode; - /// - Returns: dictionary with decoded values. `nil` if decoding failed. - func decodeReturnData(_ method: String, data: Data) -> [String: Any]? + /// - Returns: dictionary with decoded values. + /// - Throws: + /// - `Web3Error.revert(String, String?)` when function call aborted by `revert(string)` and `require(expression, string)`. + /// - `Web3Error.revertCustom(String, Dictionary)` when function call aborted by `revert CustomError()`. + @discardableResult + func decodeReturnData(_ method: String, data: Data) throws -> [String: Any] /// Decode input arguments of a function. /// - Parameters: @@ -280,6 +284,13 @@ extension DefaultContractProtocol { return encodedData } + public func event(_ event: String, parameters: [Any]) -> [EventFilterParameters.Topic?] { + guard let event = events[event] else { + return [] + } + return event.encodeParameters(parameters) + } + public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) { for (eName, ev) in self.events { if !ev.anonymous { @@ -313,13 +324,40 @@ extension DefaultContractProtocol { return bloom.test(topic: event.topic) } - public func decodeReturnData(_ method: String, data: Data) -> [String: Any]? { + @discardableResult + public func decodeReturnData(_ method: String, data: Data) throws -> [String: Any] { if method == "fallback" { - return [String: Any]() + return [:] + } + + guard let function = methods[method]?.first else { + throw Web3Error.inputError(desc: "Make sure ABI you use contains '\(method)' method.") + } + + switch data.count % 32 { + case 0: + return try function.decodeReturnData(data) + case 4: + let selector = data[0..<4] + if selector.toHexString() == "08c379a0", let reason = ABI.Element.EthError.decodeStringError(data[4...]) { + throw Web3Error.revert("revert(string)` or `require(expression, string)` was executed. reason: \(reason)", reason: reason) + } + else if selector.toHexString() == "4e487b71", let reason = ABI.Element.EthError.decodePanicError(data[4...]) { + let panicCode = String(format: "%02X", Int(reason)).addHexPrefix() + throw Web3Error.revert("Error: call revert exception; VM Exception while processing transaction: reverted with panic code \(panicCode)", reason: panicCode) + } + else if let customError = errors[selector.toHexString().addHexPrefix().lowercased()] { + if let errorArgs = customError.decodeEthError(data[4...]) { + throw Web3Error.revertCustom(customError.signature, errorArgs) + } else { + throw Web3Error.inputError(desc: "Signature matches \(customError.errorDeclaration) but failed to be decoded.") + } + } else { + throw Web3Error.inputError(desc: "Make sure ABI you use contains error that can match signature: 0x\(selector.toHexString())") + } + default: + throw Web3Error.inputError(desc: "Given data has invalid bytes count.") } - return methods[method]?.compactMap({ function in - return function.decodeReturnData(data) - }).first } public func decodeInputData(_ method: String, data: Data) -> [String: Any]? { @@ -339,8 +377,32 @@ extension DefaultContractProtocol { return function.decodeInputData(Data(data[data.startIndex + 4 ..< data.startIndex + data.count])) } + public func decodeEthError(_ data: Data) -> [String: Any]? { + guard data.count >= 4, + let err = errors.first(where: { $0.value.methodEncoding == data[0..<4] })?.value else { + return nil + } + return err.decodeEthError(data[4...]) + } + public func getFunctionCalled(_ data: Data) -> ABI.Element.Function? { guard data.count >= 4 else { return nil } return methods[data[data.startIndex ..< data.startIndex + 4].toHexString().addHexPrefix()]?.first } } + +extension DefaultContractProtocol { + @discardableResult + public func callStatic(_ method: String, parameters: [Any], provider: Web3Provider) async throws -> [String: Any] { + guard let address = address else { + 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 + } + let transaction = CodableTransaction(to: address, data: data) + + let result: Data = try await APIRequest.sendRequest(with: provider, for: .call(transaction, .latest)).result + return try decodeReturnData(method, data: result) + } +} diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 5dca0b331..4c58f08e7 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -202,7 +202,7 @@ extension ABI.Element.Constructor { extension ABI.Element.Function { /// Encode parameters of a given contract method - /// - Parameter parameters: Parameters to pass to Ethereum contract + /// - Parameters: Parameters to pass to Ethereum contract /// - Returns: Encoded data public func encodeParameters(_ parameters: [Any]) -> Data? { guard parameters.count == inputs.count, @@ -211,13 +211,123 @@ extension ABI.Element.Function { } } -// MARK: - Event logs decoding +// MARK: - Event logs decoding & encoding extension ABI.Element.Event { public func decodeReturnedLogs(eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? { guard let eventContent = ABIDecoder.decodeLog(event: self, eventLogTopics: eventLogTopics, eventLogData: eventLogData) else { return nil } return eventContent } + + public static func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? { + switch input.type { + case .string: + guard let string = value as? String else { + return nil + } + return .string(string.sha3(.keccak256).addHexPrefix()) + case .dynamicBytes: + guard let data = ABIEncoder.convertToData(value) else { + return nil + } + return .string(data.sha3(.keccak256).toHexString().addHexPrefix()) + case .bytes(length: _): + guard let data = ABIEncoder.convertToData(value), let data = data.setLengthLeft(32) else { + return nil + } + return .string(data.toHexString().addHexPrefix()) + case .address, .uint(bits: _), .int(bits: _), .bool: + guard let encoded = ABIEncoder.encodeSingleType(type: input.type, value: value) else { + return nil + } + return .string(encoded.toHexString().addHexPrefix()) + default: + guard let data = try? ABIEncoder.abiEncode(value).setLengthLeft(32) else { + return nil + } + return .string(data.toHexString().addHexPrefix()) + } + } + + public func encodeParameters(_ parameters: [Any?]) -> [EventFilterParameters.Topic?] { + guard parameters.count <= inputs.count else { + // too many arguments for fragment + return [] + } + var topics: [EventFilterParameters.Topic?] = [] + + if !anonymous { + topics.append(.string(topic.toHexString().addHexPrefix())) + } + + for (i, p) in parameters.enumerated() { + let input = inputs[i] + if !input.indexed { + // cannot filter non-indexed parameters; must be null + return [] + } + if p == nil { + topics.append(nil) + } else if input.type.isArray || input.type.isTuple { + // filtering with tuples or arrays not supported + return [] + } else if let p = p as? Array { + topics.append(.strings(p.map { Self.encodeTopic(input: input, value: $0) })) + } else { + topics.append(Self.encodeTopic(input: input, value: p!)) + } + } + + // Trim off trailing nulls + while let last = topics.last { + if last == nil { + topics.removeLast() + } else if case .string(let string) = last, string == nil { + topics.removeLast() + } else { + break + } + } + return topics + } +} + +// MARK: - Decode custom error + +extension ABI.Element.EthError { + /// Decodes `revert CustomError(_)` calls. + /// - Parameters: + /// - 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, + let decoded = ABIDecoder.decode(types: inputs, data: data) else { + return nil + } + + var result = [String: Any]() + for (index, out) in inputs.enumerated() { + result["\(index)"] = decoded[index] + if !out.name.isEmpty { + result[out.name] = decoded[index] + } + } + return result + } + + /// Decodes `revert(string)` or `require(expression, string)` calls. + /// These calls are decomposed as `Error(string)` error. + public static func decodeStringError(_ data: Data) -> String? { + let decoded = ABIDecoder.decode(types: [.init(name: "", type: .string)], data: data) + return decoded?.first as? String + } + + /// Decodes `Panic(uint256)` errors. + /// See more about panic code explain at: https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require + public static func decodePanicError(_ data: Data) -> BigUInt? { + let decoded = ABIDecoder.decode(types: [.init(name: "", type: .uint(bits: 256))], data: data) + return decoded?.first as? BigUInt + } } // MARK: - Function input/output decoding @@ -232,7 +342,7 @@ extension ABI.Element { case .fallback: return nil case .function(let function): - return function.decodeReturnData(data) + return try? function.decodeReturnData(data) case .receive: return nil case .error: @@ -265,74 +375,38 @@ extension ABI.Element.Function { return ABIDecoder.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs) } - /// Decodes data returned by a function call. Able to decode `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls. + /// Decodes data returned by a function call. /// - Parameters: /// - data: bytes returned by a function call; - /// - errors: optional dictionary of known errors that could be returned by the function you called. Used to decode the error information. /// - Returns: a dictionary containing decoded data mappend to indices and names of returned values if these are not `nil`. - /// If `data` is an error response returns dictionary containing all available information about that specific error. Read more for details. + /// - Throws: + /// - `Web3Error.processingError(desc: String)` when decode process failed. /// /// Return cases: - /// - when no `outputs` declared and `data` is not an error response: + /// - when no `outputs` declared: /// ```swift - /// ["_success": true] + /// [:] /// ``` /// - when `outputs` declared and decoding completed successfully: /// ```swift - /// ["_success": true, "0": value_1, "1": value_2, ...] + /// ["0": value_1, "1": value_2, ...] /// ``` /// Additionally this dictionary will have mappings to output names if these names are specified in the ABI; - /// - function call was aborted using `revert(message)` or `require(expression, message)`: - /// ```swift - /// ["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]` - /// ``` - /// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type: - /// ```swift - /// ["_success": false, - /// "_abortedByRevertOrRequire": true, - /// "_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)` - /// "0": error_arg1, - /// "1": error_arg2, - /// ..., - /// "error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress` - /// "error_arg2_name": error_arg2, // Otherwise, you can query them by position index. - /// ...] - /// ``` - /// - in case of any error: - /// ```swift - /// ["_success": false, "_failureReason": String] - /// ``` - /// Error reasons include: - /// - `outputs` declared but at least one value failed to be decoded; - /// - `data.count` is less than `outputs.count * 32`; - /// - `outputs` defined and `data` is empty; - /// - `data` represent reverted transaction - /// - /// How `revert(string)` and `require(expression, string)` return value is decomposed: - /// - `08C379A0` function selector for `Error(string)`; - /// - next 32 bytes are the data offset; - /// - next 32 bytes are the error message length; - /// - the next N bytes, where N >= 32, are the message bytes - /// - the rest are 0 bytes padding. - public func decodeReturnData(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any] { - if let decodedError = decodeErrorResponse(data, errors: errors) { - return decodedError - } - + public func decodeReturnData(_ data: Data) throws -> [String: Any] { guard !outputs.isEmpty else { NSLog("Function doesn't have any output types to decode given data.") - return ["_success": true] + return [:] } guard outputs.count * 32 <= data.count else { - return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."] + throw Web3Error.processingError(desc: "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail.") } // TODO: need improvement - we should be able to tell which value failed to be decoded guard let values = ABIDecoder.decode(types: outputs, data: data) else { - return ["_success": false, "_failureReason": "Failed to decode at least one value."] + throw Web3Error.processingError(desc: "Failed to decode at least one value.") } - var returnArray: [String: Any] = ["_success": true] + var returnArray: [String: Any] = [:] for i in outputs.indices { returnArray["\(i)"] = values[i] if !outputs[i].name.isEmpty { @@ -381,6 +455,7 @@ extension ABI.Element.Function { /// // "_parsingError" is optional and is present only if decoding of custom error arguments failed /// "_parsingError": "Data matches MyCustomError(uint256, address senderAddress) but failed to be decoded."] /// ``` + @available(*, deprecated, message: "Use decode function from `ABI.Element.EthError` instead") public func decodeErrorResponse(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any]? { /// If data is empty and outputs are expected it is treated as a `require(expression)` or `revert()` call with no message. /// In solidity `require(false)` and `revert()` calls return empty error response. diff --git a/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift b/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift index eb536237e..7abd04ed1 100755 --- a/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift +++ b/Sources/Web3Core/EthereumABI/ABIParameterTypes.swift @@ -168,31 +168,79 @@ extension ABI.Element.ParameterType: Equatable { } extension ABI.Element.Function { + /// String representation of a function, e.g. `transfer(address,uint256)`. public var signature: String { return "\(name ?? "")(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" } + /// Function selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + @available(*, deprecated, renamed: "selector", message: "Please, use 'selector' property instead.") public var methodString: String { + return selector + } + + /// Function selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + public var selector: String { return String(signature.sha3(.keccak256).prefix(8)) } + /// Function selector (e.g. `0xcafe1234`) but as raw bytes. + @available(*, deprecated, renamed: "selectorEncoded", message: "Please, use 'selectorEncoded' property instead.") public var methodEncoding: Data { - return signature.data(using: .ascii)!.sha3(.keccak256)[0...3] + return selectorEncoded + } + + /// Function selector (e.g. `0xcafe1234`) but as raw bytes. + public var selectorEncoded: Data { + return Data.fromHex(selector)! } } // MARK: - Event topic extension ABI.Element.Event { + /// String representation of an event, e.g. `ContractCreated(address)`. public var signature: String { return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" } + /// Hashed signature of an event, e.g. `0xcf78cf0d6f3d8371e1075c69c492ab4ec5d8cf23a1a239b6a51a1d00be7ca312`. public var topic: Data { return signature.data(using: .ascii)!.sha3(.keccak256) } } +extension ABI.Element.EthError { + /// String representation of an error, e.g. `TrasferFailed(address)`. + public var signature: String { + return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" + } + + /// Error selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + @available(*, deprecated, renamed: "selector", message: "Please, use 'selector' property instead.") + public var methodString: String { + return selector + } + + /// Error selector, e.g. `"cafe1234"`. Without hex prefix `0x`. + public var selector: String { + return String(signature.sha3(.keccak256).prefix(8)) + } + + /// Error selector (e.g. `0xcafe1234`) but as raw bytes. + @available(*, deprecated, renamed: "selectorEncoded", message: "Please, use 'selectorEncoded' property instead.") + public var methodEncoding: Data { + return selectorEncoded + } + + /// Error selector (e.g. `0xcafe1234`) but as raw bytes. + public var selectorEncoded: Data { + return Data.fromHex(selector)! + } +} + extension ABI.Element.ParameterType: ABIEncoding { + + /// Returns a valid solidity type like `address`, `uint128` or any other built-in type from Solidity. public var abiRepresentation: String { switch self { case .uint(let bits): diff --git a/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift b/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift index 1d2f32744..390b928ee 100644 --- a/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift +++ b/Sources/Web3Core/EthereumABI/Sequence+ABIExtension.swift @@ -56,6 +56,8 @@ public extension Sequence where Element == ABI.Element { var errors = [String: ABI.Element.EthError]() for case let .error(error) in self { errors[error.name] = error + errors[error.signature] = error + errors[error.methodString.addHexPrefix().lowercased()] = error } return errors } diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift index cfa7f5190..fc711ce4b 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+ComputedProperties.swift @@ -8,15 +8,12 @@ import Foundation extension APIRequest { - var method: REST { + public var method: REST { .POST } - public var encodedBody: Data { - let request = RequestBody(method: call, params: parameters) - // this is safe to force try this here - // Because request must failed to compile if it not conformable with `Encodable` protocol - return try! JSONEncoder().encode(request) + public var encodedBody: Data { + RequestBody(method: call, params: parameters).encodedBody } var parameters: [RequestParameter] { diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift index 58e13aa0f..c25ee496d 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+Methods.swift @@ -8,22 +8,116 @@ import Foundation import BigInt +/// TODO: should we do more error explain like ethers.js? +/// https://github.com/ethers-io/ethers.js/blob/0bfa7f497dc5793b66df7adfb42c6b846c51d794/packages/providers/src.ts/json-rpc-provider.ts#L55 +func checkError(method: String, error: JsonRpcErrorObject.RpcError) throws -> String { + if method == "eth_call" { + if let result = spelunkData(value: error) { + return result.data + } + throw Web3Error.nodeError(desc: "Error data decoding failed: missing revert data in exception; Transaction reverted without a reason string.") + } + + throw Web3Error.nodeError(desc: error.message) +} + +func spelunkData(value: Any?) -> (message: String, data: String)? { + if (value == nil) { + return nil + } + + func spelunkRpcError(_ message: String, data: String) -> (message: String, data: String)? { + if message.contains("revert") && data.isHex { + return (message, data) + } else { + return nil + } + } + + if let error = value as? JsonRpcErrorObject.RpcError { + if let data = error.data as? String { + return spelunkRpcError(error.message, data: data) + } else { + return spelunkData(value: error.data) + } + } + + // Spelunk further... + if let object = value as? [String: Any] { + if let message = object["message"] as? String, + let data = object["data"] as? String { + return spelunkRpcError(message, data: data) + } + + for value in object.values { + if let result = spelunkData(value: value) { + return result + } + return nil + } + } + if let array = value as? [Any] { + for e in array { + if let result = spelunkData(value: e) { + return result + } + return nil + } + } + + // Might be a JSON string we can further descend... + if let string = value as? String, let data = string.data(using: .utf8) { + let json = try? JSONSerialization.jsonObject(with: data) + return spelunkData(value: json) + } + + return nil +} + extension APIRequest { public static func sendRequest(with provider: Web3Provider, for call: APIRequest) async throws -> APIResponse { - let request = setupRequest(for: call, with: provider) - return try await APIRequest.send(uRLRequest: request, with: provider.session) + try await send(call.call, parameters: call.parameters, with: provider) } - static func setupRequest(for call: APIRequest, with provider: Web3Provider) -> URLRequest { + static func setupRequest(for body: RequestBody, with provider: Web3Provider) -> URLRequest { var urlRequest = URLRequest(url: provider.url, cachePolicy: .reloadIgnoringCacheData) urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") - urlRequest.httpMethod = call.method.rawValue - urlRequest.httpBody = call.encodedBody + urlRequest.httpMethod = "POST" + urlRequest.httpBody = body.encodedBody return urlRequest } - public static func send(uRLRequest: URLRequest, with session: URLSession) async throws -> APIResponse { + public static func send(_ method: String, parameters: [Encodable], with provider: Web3Provider) async throws -> APIResponse { + let body = RequestBody(method: method, params: parameters) + let uRLRequest = setupRequest(for: body, with: provider) + + let data: Data + do { + data = try await send(uRLRequest: uRLRequest, with: provider.session) + } catch Web3Error.rpcError(let error) { + let responseAsString = try checkError(method: method, error: error) + guard let LiteralType = Result.self as? LiteralInitiableFromString.Type, + let literalValue = LiteralType.init(from: responseAsString), + let result = literalValue as? Result else { + throw Web3Error.dataError + } + 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) + } + + public static func send(uRLRequest: URLRequest, with session: URLSession) async throws -> Data { let (data, response) = try await session.data(for: uRLRequest) guard 200 ..< 400 ~= response.statusCode else { @@ -34,9 +128,9 @@ extension APIRequest { } } - if let error = (try? JSONDecoder().decode(JsonRpcErrorObject.self, from: data))?.error { + if let error = JsonRpcErrorObject.init(from: data)?.error { guard let parsedErrorCode = error.parsedErrorCode else { - throw Web3Error.nodeError(desc: "\(error.message)\nError code: \(error.code)") + throw Web3Error.rpcError(error) } let description = "\(parsedErrorCode.errorName). Error code: \(error.code). \(error.message)" switch parsedErrorCode { @@ -49,35 +143,51 @@ extension APIRequest { } } - /// This bit of code is purposed to work with literal types that comes in ``Response`` in hexString type. - /// Currently it's just `Data` and any kind of Integers `(U)Int`, `Big(U)Int`. - 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`, that conforming to an `APIResponseType` type, so it's 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 data } } /// JSON RPC Error object. See official specification https://www.jsonrpc.org/specification#error_object -private struct JsonRpcErrorObject: Decodable { +public struct JsonRpcErrorObject { public let error: RpcError? - class RpcError: Decodable { - let message: String - let code: Int + public class RpcError { + public let message: String + public let code: Int + public let data: Any? + + init(message: String, code: Int, data: Any?) { + self.message = message + self.code = code + self.data = data + } + var parsedErrorCode: JsonRpcErrorCode? { JsonRpcErrorCode.from(code) } } + + init?(from data: Data) { + guard let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + return nil + } + if let error = root["error"] as? [String: Any], + let message = error["message"] as? String, + let code = error["code"] as? Int { + guard let errorData = error["data"] else { + self.error = RpcError(message: message, code: code, data: nil) + return + } + self.error = RpcError(message: message, code: code, data: errorData) + } else { + self.error = nil + } + } } /// For error codes specification see chapter `5.1 Error object` /// https://www.jsonrpc.org/specification#error_object -private enum JsonRpcErrorCode { +enum JsonRpcErrorCode { /// -32700 /// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON case parseError diff --git a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift index ca55d622f..1e40e53d5 100644 --- a/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift +++ b/Sources/Web3Core/EthereumNetwork/Request/APIRequest+UtilityTypes.swift @@ -14,7 +14,7 @@ public struct APIResponse: Decodable where Result: APIResultType { public var result: Result } -enum REST: String { +public enum REST: String { case POST case GET } @@ -24,5 +24,30 @@ struct RequestBody: Encodable { var id = Counter.increment() var method: String - var params: [RequestParameter] + var params: [Encodable] + + enum CodingKeys: String, CodingKey { + case jsonrpc + case id + case method + case params + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(jsonrpc, forKey: .jsonrpc) + try container.encode(id, forKey: .id) + try container.encode(method, forKey: .method) + + var paramsContainer = container.superEncoder(forKey: .params).unkeyedContainer() + try params.forEach { a in + try paramsContainer.encode(a) + } + } + + public var encodedBody: Data { + // Safe to use force-try because request will fail to + // compile if it's not conforming to the `Encodable` protocol. + return try! JSONEncoder().encode(self) + } } diff --git a/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift b/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift index d64bb9a09..739442fca 100644 --- a/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift +++ b/Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift @@ -17,16 +17,17 @@ extension BigInt: LiteralInitiableFromString { } extension BigUInt: LiteralInitiableFromString { } extension Data: LiteralInitiableFromString { + /// Converts hexadecimal string representation of some bytes into actual bytes. + /// Notes: + /// - empty string will return `nil`; + /// - empty hex string, meaning it's equal to `"0x"`, will return empty `Data` object. + /// - Parameter hex: bytes represented as string. + /// - Returns: optional raw bytes. public static func fromHex(_ hex: String) -> Data? { - let string = hex.lowercased().stripHexPrefix() - let array = [UInt8](hex: string) - if array.count == 0 { - if hex == "0x" || hex == "" { - return Data() - } else { - return nil - } - } - return Data(array) + let hex = hex.lowercased().trim() + guard !hex.isEmpty else { return nil } + guard hex != "0x" else { return Data() } + let bytes = [UInt8](hex: hex.stripHexPrefix()) + return bytes.isEmpty ? nil : Data(bytes) } } diff --git a/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift b/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift index b4a3c764d..8cedb7985 100755 --- a/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift +++ b/Sources/Web3Core/KeystoreManager/BIP32Keystore.swift @@ -149,71 +149,76 @@ public class BIP32Keystore: AbstractKeystore { } else { newIndex = UInt32.zero } + guard let newNode = parentNode.derive(index: newIndex, derivePrivateKey: true, hardened: false) else { throw AbstractKeystoreError.keyDerivationError } guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else { throw AbstractKeystoreError.keyDerivationError } - let prefixPath = self.rootPrefix - var newPath: String - if newNode.isHardened { - newPath = prefixPath + "/" + String(newNode.index % HDNode.hardenedIndexPrefix) + "'" - } else { - newPath = prefixPath + "/" + String(newNode.index) - } + let newPath = rootPrefix + "/" + String(newNode.index) addressStorage.add(address: newAddress, for: newPath) } public func createNewCustomChildAccount(password: String, path: String) throws { - guard let decryptedRootNode = try getPrefixNodeData(password) else { + guard let decryptedRootNode = try getPrefixNodeData(password), + let keystoreParams else { throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore") } guard let rootNode = HDNode(decryptedRootNode) else { throw AbstractKeystoreError.encryptionError("Failed to deserialize a root node") } - let prefixPath = self.rootPrefix - var pathAppendix: String? + + let prefixPath = rootPrefix + var pathAppendix = path + if path.hasPrefix(prefixPath) { - let upperIndex = (path.range(of: prefixPath)?.upperBound)! - if upperIndex < path.endIndex { - pathAppendix = String(path[path.index(after: upperIndex)]) + if let upperIndex = (path.range(of: prefixPath)?.upperBound), upperIndex < path.endIndex { + pathAppendix = String(path[path.index(after: upperIndex).. [EthereumAddress] { + guard let decryptedRootNode = try? getPrefixNodeData(password), + let rootNode = HDNode(decryptedRootNode) else { + throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore") + } + return try [UInt](0..(fromArray values: [T]) { let values = values let ptrUB = values.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer) in return ptr } @@ -33,32 +34,34 @@ extension Data { return difference == UInt8(0x00) } - public static func zero(_ data: inout Data) { + static func zero(_ data: inout Data) { let count = data.count data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in body.baseAddress?.assumingMemoryBound(to: UInt8.self).initialize(repeating: 0, count: count) } } - public static func randomBytes(length: Int) -> Data? { - for _ in 0...1024 { - var data = Data(repeating: 0, count: length) - let result = data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) -> Int32? in - if let bodyAddress = body.baseAddress, body.count > 0 { - let pointer = bodyAddress.assumingMemoryBound(to: UInt8.self) - return SecRandomCopyBytes(kSecRandomDefault, length, pointer) - } else { - return nil - } - } - if let notNilResult = result, notNilResult == errSecSuccess { - return data - } + /** + Generates an array of random bytes of the specified length. + This function uses `SecRandomCopyBytes` to generate random bytes returning it as a `Data` object. + If an error occurs during random bytes generation, the function returns `nil`. + Error occurs only if `SecRandomCopyBytes` returns status that is not `errSecSuccess`. + See [all status codes](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes) for possible error reasons. + Note: in v4 of web3swift this function will be deprecated and a new implementation will be provided that will throw occurred error. + - Parameter length: The number of random bytes to generate. + + - Returns: optional `Data` object containing the generated random bytes, or `nil` if an error occurred during generation. + */ + static func randomBytes(length: Int) -> Data? { + var entropyBytes = [UInt8](repeating: 0, count: length) + let status = SecRandomCopyBytes(kSecRandomDefault, entropyBytes.count, &entropyBytes) + guard status == errSecSuccess else { + return nil } - return nil + return Data(entropyBytes) } - public func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public + func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public if startingBit + length / 8 > self.count, length > 64, startingBit > 0, length >= 1 { return nil } let bytes = self[(startingBit/8) ..< (startingBit+length+7)/8] let padding = Data(repeating: 0, count: 8 - bytes.count) diff --git a/Sources/Web3Core/Utility/String+Extension.swift b/Sources/Web3Core/Utility/String+Extension.swift index dbe0a10ca..778558166 100755 --- a/Sources/Web3Core/Utility/String+Extension.swift +++ b/Sources/Web3Core/Utility/String+Extension.swift @@ -120,7 +120,7 @@ extension String { let to16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location + nsRange.length, limitedBy: utf16.endIndex), let from = from16.samePosition(in: self), let to = to16.samePosition(in: self) - else { return nil } + else { return nil } return from ..< to } @@ -135,6 +135,40 @@ extension String { func trim() -> String { trimmingCharacters(in: .whitespacesAndNewlines) } + + public var isHex: Bool { + var _str = self.trim() + if _str.isEmpty { + return false + } + _str = _str.stripHexPrefix() + for char in _str { + if !char.isHexDigit { + return false + } + } + return true + } + + /// Splits a string into groups of `every` n characters, grouping from left-to-right by default. If `backwards` is true, right-to-left. + public func split(every: Int, backwards: Bool = false) -> [String] { + var result = [String]() + + for i in stride(from: 0, to: self.count, by: every) { + switch backwards { + case true: + let endIndex = self.index(self.endIndex, offsetBy: -i) + let startIndex = self.index(endIndex, offsetBy: -every, limitedBy: self.startIndex) ?? self.startIndex + result.insert(String(self[startIndex.. [EventLog] { + try await APIRequest.sendRequest(with: self.provider, for: .getLogs(eventFilter)).result + } +} + public extension IEth { func send(_ transaction: CodableTransaction) async throws -> TransactionSendingResult { let request = APIRequest.sendTransaction(transaction) diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift index ddade0c00..0ce33372f 100755 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift @@ -25,6 +25,8 @@ public protocol IEth { func code(for address: EthereumAddress, onBlock: BlockNumber) async throws -> Hash + func getLogs(eventFilter: EventFilterParameters) async throws -> [EventLog] + func gasPrice() async throws -> BigUInt func getTransactionCount(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt diff --git a/Sources/web3swift/Operations/ReadOperation.swift b/Sources/web3swift/Operations/ReadOperation.swift index 8245e1c78..c2b9365ca 100755 --- a/Sources/web3swift/Operations/ReadOperation.swift +++ b/Sources/web3swift/Operations/ReadOperation.swift @@ -43,9 +43,6 @@ public class ReadOperation { let resultHex = data.toHexString().addHexPrefix() return ["result": resultHex] } - guard let decodedData = self.contract.decodeReturnData(self.method, data: data) else { - throw Web3Error.processingError(desc: "Can not decode returned parameters") - } - return decodedData + return try self.contract.decodeReturnData(self.method, data: data) } } diff --git a/Sources/web3swift/Web3/Web3+Contract.swift b/Sources/web3swift/Web3/Web3+Contract.swift index 7bf192710..de01b10ba 100755 --- a/Sources/web3swift/Web3/Web3+Contract.swift +++ b/Sources/web3swift/Web3/Web3+Contract.swift @@ -47,7 +47,7 @@ extension Web3 { // MARK: Writing Data flow // FIXME: Rewrite this to CodableTransaction - /// Deploys a constact instance using the previously provided ABI, some bytecode, constructor parameters and options. + /// Deploys a contract instance using the previously provided ABI, some bytecode, constructor parameters and options. /// If extraData is supplied it is appended to encoded bytecode and constructor parameters. /// /// Returns a "Transaction intermediate" object. @@ -112,5 +112,11 @@ extension Web3 { } return .init(transaction: transaction, web3: web3, contract: contract, method: method) } + + /// Combines `createReadOperation` & `callContractMethod` + @discardableResult + public func callStatic(_ method: String, parameters: [Any]) async throws -> [String: Any] { + try await contract.callStatic(method, parameters: parameters, provider: web3.provider) + } } } diff --git a/Sources/web3swift/Web3/Web3+HttpProvider.swift b/Sources/web3swift/Web3/Web3+HttpProvider.swift index a6411552d..1e75b242c 100755 --- a/Sources/web3swift/Web3/Web3+HttpProvider.swift +++ b/Sources/web3swift/Web3/Web3+HttpProvider.swift @@ -33,13 +33,21 @@ public class Web3HttpProvider: Web3Provider { if let net = net { network = net } else { - var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData) - urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") - urlRequest.setValue("application/json", forHTTPHeaderField: "Accept") - urlRequest.httpMethod = APIRequest.getNetwork.call - urlRequest.httpBody = APIRequest.getNetwork.encodedBody - let response: APIResponse = try await APIRequest.send(uRLRequest: urlRequest, with: session) - self.network = Networks.fromInt(response.result) + /// chain id could be a hex string or an int value. + let response: String = try await APIRequest.send(APIRequest.getNetwork.call, parameters: [], with: self).result + let result: UInt + if response.hasHexPrefix() { + guard let num = BigUInt(response, radix: 16) else { + throw Web3Error.processingError(desc: "Get network succeeded but can't be parsed to a valid chain id.") + } + result = UInt(num) + } else { + guard let num = UInt(response) else { + throw Web3Error.processingError(desc: "Get network succeeded but can't be parsed to a valid chain id.") + } + result = num + } + self.network = Networks.fromInt(result) } attachedKeystoreManager = manager } diff --git a/Sources/web3swift/Web3/Web3+Signing.swift b/Sources/web3swift/Web3/Web3+Signing.swift index 2e135c6c0..46fe01ce5 100755 --- a/Sources/web3swift/Web3/Web3+Signing.swift +++ b/Sources/web3swift/Web3/Web3+Signing.swift @@ -23,11 +23,18 @@ public struct Web3Signer { keystore: T, account: EthereumAddress, password: String, + useHash: Bool = true, useExtraEntropy: Bool = false) throws -> Data? { var privateKey = try keystore.UNSAFE_getPrivateKeyData(password: password, account: account) defer { Data.zero(&privateKey) } - guard let hash = Utilities.hashPersonalMessage(personalMessage) else { return nil } - let (compressedSignature, _) = SECP256K1.signForRecovery(hash: hash, + var data: Data + if useHash { + guard let hash = Utilities.hashPersonalMessage(personalMessage) else { return nil } + data = hash + } else { + data = personalMessage + } + let (compressedSignature, _) = SECP256K1.signForRecovery(hash: data, privateKey: privateKey, useExtraEntropy: useExtraEntropy) return compressedSignature diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift index a3fbeb43c..3e7f077d8 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -27,7 +27,7 @@ final class ABIDecoderSliceTests: XCTestCase { while startIndex < data.count { let slice = data[startIndex ..< startIndex + answerSize] startIndex += answerSize - guard let bigInt = balanceofMethod.decodeReturnData(slice)["0"] as? BigUInt else { + guard let bigInt = try balanceofMethod.decodeReturnData(slice)["0"] as? BigUInt else { throw Web3Error.processingError(desc: "Can not decode returned parameters") } let value = Utilities.formatToPrecision(bigInt, units: .wei) @@ -52,9 +52,7 @@ final class ABIDecoderSliceTests: XCTestCase { XCTAssertEqual(methods.count, 3) /// Act - guard let decodedData = multiCall2Contract.decodeReturnData("aggregate", data: data) else { - throw Web3Error.processingError(desc: "Can not decode returned parameters") - } + let decodedData = try multiCall2Contract.decodeReturnData("aggregate", data: data) guard let returnData = decodedData["returnData"] as? [Data] else { throw Web3Error.dataError @@ -63,7 +61,7 @@ final class ABIDecoderSliceTests: XCTestCase { XCTAssertEqual(returnData.count, 3) for item in methods.enumerated() { - XCTAssertNotNil(item.element.decodeReturnData(returnData[item.offset])["0"]) + XCTAssertNotNil(try item.element.decodeReturnData(returnData[item.offset])["0"]) } } @@ -74,9 +72,7 @@ final class ABIDecoderSliceTests: XCTestCase { let erc20_balanceof = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! /// Act - guard let decodedData = contract.decodeReturnData("tryAggregate", data: data) else { - throw Web3Error.processingError(desc: "Can not decode returned parameters") - } + let decodedData = try contract.decodeReturnData("tryAggregate", data: data) guard let returnData = decodedData["returnData"] as? [[Any]] else { throw Web3Error.dataError @@ -84,7 +80,7 @@ final class ABIDecoderSliceTests: XCTestCase { var resultArray = [BigUInt]() for i in 0..<2 { guard let data = returnData[i][1] as? Data, - let balance = erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { + let balance = try? erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { resultArray.append(0) continue } diff --git a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift index 181337d19..d0aa1b9d8 100644 --- a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift +++ b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift @@ -65,123 +65,140 @@ class ABIElementErrorDecodingTest: XCTestCase { XCTAssertTrue(emptyFunction.decodeErrorResponse(Data()) == nil) } - func testDecodeEmptyErrorOnOneOutputFunction() { - guard let errorData = oneOutputFunction.decodeErrorResponse(Data()) else { - XCTFail("Empty Data must be decoded as a `revert()` or `require(false)` call if function used to decode it has at least one output parameter.") - return + /// `require(expression)` and `revert()` without a message return 0 bytes, + /// we can noly catch an error when function has a return value + func testDecodeEmptyErrorOnOneOutputFunction() throws { + let contract = try EthereumContract(abi: [.function(emptyFunction)]) + do { + try contract.decodeReturnData(emptyFunction.signature, data: Data()) + } catch { + XCTFail() } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertNotNil(errorData["_failureReason"] as? String) - - let decodedOutput = oneOutputFunction.decodeReturnData(Data()) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_failureReason"] as? String, decodedOutput["_failureReason"] as? String) + let contract2 = try EthereumContract(abi: [.function(oneOutputFunction)]) + do { + try contract2.decodeReturnData(oneOutputFunction.signature, data: Data()) + XCTFail() + } catch { + print(error) + } } /// Data is decoded as a call of `revert` or `require` with a message no matter the number of outputs configured in the ``ABI/Element/Function``. /// `revert(message)` and `require(false,message)`return at least 128 bytes. We cannot differentiate between `require` or `revert`. - func testDecodeDefaultErrorWithMessage() { + func testDecodeDefaultErrorWithMessage() throws { /// 08c379a0 - Error(string) function selector /// 0000000000000000000000000000000000000000000000000000000000000020 - Data offset /// 000000000000000000000000000000000000000000000000000000000000001a - Message length /// 4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 - Message + 0 bytes padding /// 0000... - some more 0 bytes padding to make the number of bytes match 32 bytes chunks - let errorResponse = Data.fromHex("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a4e6f7420656e6f7567682045746865722070726f76696465642e00000000000000000000000000000000000000000000000000000000000000000000")! - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a4e6f7420656e6f7567682045746865722070726f76696465642e0000000000000000000000000000000000000000000000000000000000000000000000000000")! + let contract = try EthereumContract(abi: [.function(emptyFunction)]) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revert(_, let reason) { + XCTAssertEqual(reason, "Not enough Ether provided.") } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_errorMessage"] as? String, "Not enough Ether provided.") - XCTAssertNotNil(errorData["_failureReason"] as? String) - - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_failureReason"] as? String, decodedOutput["_failureReason"] as? String) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_errorMessage"] as? String, decodedOutput["_errorMessage"] as? String) - XCTAssertEqual(decodedOutput["_errorMessage"] as? String, "Not enough Ether provided.") + XCTAssertEqual(EthError.decodeStringError(errorResponse[4...]), "Not enough Ether provided.") } - /// Data is decoded as a call of `revert Unauthorized()`. Decoded only if custom error ABI is given. - func testDecodeRevertWithCustomError() { + /// Data is decoded as a call of `revert Unauthorized()` + func testDecodeRevertWithCustomError() throws { /// 82b42900 - Unauthorized() function selector /// 00000000000000000000000000000000000000000000000000000000 - padding bytes - let errorResponse = Data.fromHex("82b4290000000000000000000000000000000000000000000000000000000000")! - let errors: [String: EthError] = ["82b42900": .init(name: "Unauthorized", inputs: [])] - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("82b429000000000000000000000000000000000000000000000000000000000000000000")! + let error = ABI.Element.EthError(name: "Unauthorized", inputs: []) + let contract = try EthereumContract(abi: [.function(emptyFunction), .error(error)] ) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "Unauthorized()") + XCTAssertTrue(args.isEmpty) } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_error"] as? String, "Unauthorized()") - - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) + guard let decoded = error.decodeEthError(errorResponse[4...]) else { + XCTFail("decode response failed.") + return + } + XCTAssertTrue(decoded.isEmpty) } - /// Data is decoded as a call of `revert Unauthorized()`. Decoded only if custom error ABI is given. + /// Data is decoded as a call of `revert Unauthorized(bool)`. /// Trying to decode as `Unauthorized(string)`. Must fail. - func testDecodeRevertWithCustomErrorFailed() { - /// 82b42900 - Unauthorized() function selector + func testDecodeRevertWithCustomErrorFailed() throws { + /// 5caef992 - Unauthorized(bool) function selector /// 00000000000000000000000000000000000000000000000000000000 - padding bytes - let errorResponse = Data.fromHex("82b4290000000000000000000000000000000000000000000000000000000000")! - let errors: [String: EthError] = ["82b42900": .init(name: "Unauthorized", inputs: [.init(name: "", type: .string)])] - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("5caef9920000000000000000000000000000000000000000000000000000000000000000")! + let error = ABI.Element.EthError(name: "Unauthorized", inputs: [.init(name: "", type: .bool)]) + let contract = try EthereumContract(abi: [.function(oneOutputFunction), .error(error)] ) + + do { + try contract.decodeReturnData(oneOutputFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "Unauthorized(bool)") + XCTAssertEqual(args["0"] as? Bool, false) } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_error"] as? String, "Unauthorized(string)") - XCTAssertEqual(errorData["_parsingError"] as? String, "Data matches Unauthorized(string) but failed to be decoded.") - - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) - - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) - XCTAssertEqual(errorData["_parsingError"] as? String, decodedOutput["_parsingError"] as? String) + guard let decoded = error.decodeEthError(errorResponse[4...]) else { + XCTFail("decode response failed.") + return + } + XCTAssertEqual(decoded["0"] as? Bool, false) } /// Data is decoded as a call of `revert Unauthorized("Reason")`. Decoded only if custom error ABI is given. /// The custom error argument must be extractable by index and name if the name is available. - func testDecodeRevertWithCustomErrorWithArguments() { + func testDecodeRevertWithCustomErrorWithArguments() throws { /// 973d02cb - `Unauthorized(string)` function selector /// 0000000000000000000000000000000000000000000000000000000000000020 - data offset /// 0000000000000000000000000000000000000000000000000000000000000006 - first custom argument length /// 526561736f6e0000000000000000000000000000000000000000000000000000 - first custom argument bytes + 0 bytes padding /// 0000... - some more 0 bytes padding to make the number of bytes match 32 bytes chunks - let errorResponse = Data.fromHex("973d02cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006526561736f6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")! - let errors: [String: EthError] = ["973d02cb": .init(name: "Unauthorized", inputs: [.init(name: "message_arg", type: .string)])] - guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { - XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") - return + let errorResponse = Data.fromHex("973d02cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006526561736f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")! + let error = ABI.Element.EthError(name: "Unauthorized", inputs: [.init(name: "message_arg", type: .string)]) + let contract = try EthereumContract(abi: [.function(emptyFunction), .error(error)]) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + XCTFail("decode function should throw an error") + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "Unauthorized(string)") + XCTAssertEqual(args["0"] as? String, "Reason") + XCTAssertEqual(args["message_arg"] as? String, "Reason") } - XCTAssertEqual(errorData["_success"] as? Bool, false) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) - XCTAssertEqual(errorData["_error"] as? String, "Unauthorized(string message_arg)") - XCTAssertEqual(errorData["0"] as? String, "Reason") - XCTAssertEqual(errorData["0"] as? String, errorData["message_arg"] as? String) + guard let decoded = error.decodeEthError(errorResponse[4...]) else { + XCTFail("decode response failed.") + return + } + XCTAssertEqual(decoded["0"] as? String, "Reason") + XCTAssertEqual(decoded["message_arg"] as? String, "Reason") + } - let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) + /// Data is decoded as a panic exception is generated. + /// Example: + /// ``` solidity + /// function panicError() public { + /// assert(false); + /// } + /// ``` + func testDecodePanicError() throws { + let errorResponse = Data(hex: "4e487b710000000000000000000000000000000000000000000000000000000000000001") + let contract = try EthereumContract(abi: [.function(emptyFunction)]) + + do { + try contract.decodeReturnData(emptyFunction.signature, data: errorResponse) + } catch Web3Error.revert(let message, let code) { + XCTAssertTrue(message.contains("reverted with panic code 0x01")) + XCTAssertEqual(code, "0x01") + } - XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) - XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) - XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) - XCTAssertEqual(errorData["0"] as? String, decodedOutput["0"] as? String) - XCTAssertEqual(errorData["message_arg"] as? String, decodedOutput["message_arg"] as? String) + XCTAssertEqual(EthError.decodePanicError(errorResponse[4...]), 1) } } diff --git a/Tests/web3swiftTests/localTests/ABIElementsTests.swift b/Tests/web3swiftTests/localTests/ABIElementsTests.swift new file mode 100644 index 000000000..a94fa3d9d --- /dev/null +++ b/Tests/web3swiftTests/localTests/ABIElementsTests.swift @@ -0,0 +1,29 @@ +// +// ABIElementsTests.swift +// +// +// Created by JeneaVranceanu on 09.01.2024. +// + +import Foundation +import XCTest +@testable import web3swift +@testable import Web3Core + +class ABIElementsTests: XCTestCase { + + func testABIElementFunction() { + let test1Function = ABI.Element.Function(name: "Test1", + inputs: [], + outputs: [], + constant: true, + payable: false) + + XCTAssertEqual(test1Function.name, "Test1") + XCTAssertEqual(test1Function.selector, String("Test1()".sha3(.keccak256).prefix(8))) + XCTAssertEqual(test1Function.selectorEncoded, + Data.fromHex("Test1()".sha3(.keccak256))?.prefix(4)) + + } + +} diff --git a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift index 9828ca345..66c3749df 100755 --- a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift +++ b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift @@ -17,20 +17,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode)! - deployTx.transaction.from = allAddresses[0] - // MARK: Sending Data flow - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -39,7 +26,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testSingle") @@ -51,20 +38,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -73,7 +47,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testStaticArray") @@ -85,20 +59,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -107,7 +68,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! let tx = contract.createReadOperation("testDynArray") _ = try await tx!.call() @@ -118,20 +79,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -140,7 +88,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testDynOfDyn") @@ -152,20 +100,7 @@ class AdvancedABIv2Tests: LocalTestCase { let bytecode = Data.fromHex("6080604052341561000f57600080fd5b610cb18061001e6000396000f30060806040526004361061006d576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063189533931461007257806338163ff51461009b57806388be6c65146100c4578063e7c1a47c146100ed578063f376e01314610116575b600080fd5b341561007d57600080fd5b61008561013f565b6040516100929190610a9f565b60405180910390f35b34156100a657600080fd5b6100ae610220565b6040516100bb9190610a7d565b60405180910390f35b34156100cf57600080fd5b6100d76102c5565b6040516100e49190610ae3565b60405180910390f35b34156100f857600080fd5b610100610350565b60405161010d9190610ac1565b60405180910390f35b341561012157600080fd5b610129610399565b6040516101369190610b05565b60405180910390f35b6060600260405190808252806020026020018201604052801561017657816020015b60608152602001906001900390816101615790505b5090506040805190810160405280600581526020017f48656c6c6f0000000000000000000000000000000000000000000000000000008152508160008151811015156101be57fe5b906020019060200201819052506040805190810160405280600581526020017f576f726c6400000000000000000000000000000000000000000000000000000081525081600181518110151561021057fe5b9060200190602002018190525090565b610228610546565b6040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081600060028110151561026d57fe5b60200201819052506040805190810160405280600581526020017f576f726c640000000000000000000000000000000000000000000000000000008152508160016002811015156102ba57fe5b602002018190525090565b6060600260405190808252806020026020018201604052801561030257816020015b6102ef61056d565b8152602001906001900390816102e75790505b50905061030d610399565b81600081518110151561031c57fe5b90602001906020020181905250610331610399565b81600181518110151561034057fe5b9060200190602002018190525090565b6103586105a9565b610360610399565b81600060028110151561036f57fe5b602002018190525061037f610399565b81600160028110151561038e57fe5b602002018190525090565b6103a16105d8565b60606103ab610614565b6103b3610546565b60036040519080825280602002602001820160405280156103e35781602001602082028038833980820191505090505b50925060008360008151811015156103f757fe5b9060200190602002018181525050600183600181518110151561041657fe5b9060200190602002018181525050600283600281518110151561043557fe5b90602001906020020181815250506040805190810160405280600081526020016001815250915060408051908101604052806040805190810160405280600581526020017f48656c6c6f00000000000000000000000000000000000000000000000000000081525081526020016040805190810160405280600581526020017f576f726c64000000000000000000000000000000000000000000000000000000815250815250905060a060405190810160405280600181526020016040805190810160405280600b81526020017f48656c6c6f20776f726c64000000000000000000000000000000000000000000815250815260200183815260200184815260200182815250935083935050505090565b60408051908101604052806002905b60608152602001906001900390816105555790505090565b60e060405190810160405280600081526020016060815260200161058f610636565b8152602001606081526020016105a3610658565b81525090565b6101c0604051908101604052806002905b6105c261056d565b8152602001906001900390816105ba5790505090565b60e06040519081016040528060008152602001606081526020016105fa610636565b81526020016060815260200161060e610658565b81525090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816106675790505090565b600061068a82610b81565b8360208202850161069a85610b31565b60005b848110156106d35783830388526106b5838351610930565b92506106c082610bdb565b915060208801975060018101905061069d565b508196508694505050505092915050565b60006106ef82610b76565b836020820285016106ff85610b27565b60005b8481101561073857838303885261071a838351610930565b925061072582610bce565b9150602088019750600181019050610702565b508196508694505050505092915050565b600061075482610b8c565b8084526020840193508360208202850161076d85610b3b565b60005b848110156107a6578383038852610788838351610930565b925061079382610be8565b9150602088019750600181019050610770565b508196508694505050505092915050565b60006107c282610b97565b836020820285016107d285610b48565b60005b8481101561080b5783830388526107ed8383516109ea565b92506107f882610bf5565b91506020880197506001810190506107d5565b508196508694505050505092915050565b600061082782610ba2565b8084526020840193508360208202850161084085610b52565b60005b8481101561087957838303885261085b8383516109ea565b925061086682610c02565b9150602088019750600181019050610843565b508196508694505050505092915050565b61089381610bad565b61089c82610b5f565b60005b828110156108ce576108b2858351610a6e565b6108bb82610c0f565b915060208501945060018101905061089f565b5050505050565b60006108e082610bb8565b8084526020840193506108f283610b69565b60005b8281101561092457610908868351610a6e565b61091182610c1c565b91506020860195506001810190506108f5565b50849250505092915050565b600061093b82610bc3565b80845261094f816020860160208601610c33565b61095881610c66565b602085010191505092915050565b600060c08301600083015161097e6000860182610a6e565b50602083015184820360208601526109968282610930565b91505060408301516109ab604086018261088a565b50606083015184820360808601526109c382826108d5565b915050608083015184820360a08601526109dd82826106e4565b9150508091505092915050565b600060c083016000830151610a026000860182610a6e565b5060208301518482036020860152610a1a8282610930565b9150506040830151610a2f604086018261088a565b5060608301518482036080860152610a4782826108d5565b915050608083015184820360a0860152610a6182826106e4565b9150508091505092915050565b610a7781610c29565b82525050565b60006020820190508181036000830152610a97818461067f565b905092915050565b60006020820190508181036000830152610ab98184610749565b905092915050565b60006020820190508181036000830152610adb81846107b7565b905092915050565b60006020820190508181036000830152610afd818461081c565b905092915050565b60006020820190508181036000830152610b1f8184610966565b905092915050565b6000819050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000819050919050565b60005b83811015610c51578082015181840152602081019050610c36565b83811115610c60576000848401525b50505050565b6000601f19601f83011690509190505600a265627a7a72305820fdaf8ce6fe282a46498c8066d5b5ac382b69969eee38e1ad03c0d501b27f65366c6578706572696d656e74616cf50037")! let web3 = try await Web3.new(LocalTestCase.url) - let allAddresses = try await web3.eth.ownedAccounts() - var contract = web3.contract(abiString, at: nil, abiVersion: 2)! - - let parameters: [Any] = [] - // MARK: Writing Data flow - let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! - deployTx.transaction.from = allAddresses[0] - let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) - let txHash = result.hash.stripHexPrefix() - - Thread.sleep(forTimeInterval: 1.0) - - let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) switch receipt.status { case .notYetProcessed: @@ -174,7 +109,7 @@ class AdvancedABIv2Tests: LocalTestCase { break } - contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! + let contract = web3.contract(abiString, at: receipt.contractAddress, abiVersion: 2)! // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract.createReadOperation("testStOfDyn") @@ -182,8 +117,10 @@ class AdvancedABIv2Tests: LocalTestCase { } func testEmptyArrayDecoding() async throws { + let bytecode = Data(hex: "608060405234801561001057600080fd5b5061027b806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f2a75fe414610030575b600080fd5b61003861004e565b60405161004591906101e0565b60405180910390f35b60606000600267ffffffffffffffff811115610093577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156100c15781602001602082028036833780820191505090505b509050600181600081518110610100577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001018181525050600281600181518110610148577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010181815250508091505090565b600061016783836101d1565b60208301905092915050565b600061017e82610212565b610188818561022a565b935061019383610202565b8060005b838110156101c45781516101ab888261015b565b97506101b68361021d565b925050600181019050610197565b5085935050505092915050565b6101da8161023b565b82525050565b600060208201905081810360008301526101fa8184610173565b905092915050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b600081905091905056fea2646970667358221220d1b52dfe3238df01604ccfbb6c6cee01edbaa74fcffd8a57c4041b0b19e6887664736f6c63430008040033") let abiString = "[{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"constant\":true,\"inputs\":[],\"name\":\"empty\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]" - let contractAddress = EthereumAddress("0x200eb5ccda1c35b0f5bf82552fdd65a8aee98e79")! + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) + let contractAddress = receipt.contractAddress! let web3 = try await Web3.new(LocalTestCase.url) let contract = web3.contract(abiString, at: contractAddress, abiVersion: 2) XCTAssert(contract != nil) @@ -191,20 +128,31 @@ class AdvancedABIv2Tests: LocalTestCase { // MARK: - Encoding ABI Data flow let tx = contract?.createReadOperation("empty") XCTAssertNotNil(tx) - _ = try await tx!.call() + let result = try await tx!.call() + XCTAssertEqual(result.count, 1) + XCTAssertEqual((result["0"] as? Array)?[0], 1) + XCTAssertEqual((result["0"] as? Array)?[1], 2) } func testUserCase() async throws { + let bytecode = Data(hex: "0x608060405234801561001057600080fd5b50610484806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a16e94bf1461003b578063a46b5b6b14610059575b600080fd5b610043610075565b60405161005091906102a6565b60405180910390f35b610073600480360381019061006e919061022c565b610107565b005b6060600080546100849061037c565b80601f01602080910402602001604051908101604052809291908181526020018280546100b09061037c565b80156100fd5780601f106100d2576101008083540402835291602001916100fd565b820191906000526020600020905b8154815290600101906020018083116100e057829003601f168201915b5050505050905090565b806000908051906020019061011d929190610121565b5050565b82805461012d9061037c565b90600052602060002090601f01602090048101928261014f5760008555610196565b82601f1061016857805160ff1916838001178555610196565b82800160010185558215610196579182015b8281111561019557825182559160200191906001019061017a565b5b5090506101a391906101a7565b5090565b5b808211156101c05760008160009055506001016101a8565b5090565b60006101d76101d2846102ed565b6102c8565b9050828152602081018484840111156101ef57600080fd5b6101fa84828561033a565b509392505050565b600082601f83011261021357600080fd5b81356102238482602086016101c4565b91505092915050565b60006020828403121561023e57600080fd5b600082013567ffffffffffffffff81111561025857600080fd5b61026484828501610202565b91505092915050565b60006102788261031e565b6102828185610329565b9350610292818560208601610349565b61029b8161043d565b840191505092915050565b600060208201905081810360008301526102c0818461026d565b905092915050565b60006102d26102e3565b90506102de82826103ae565b919050565b6000604051905090565b600067ffffffffffffffff8211156103085761030761040e565b5b6103118261043d565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b8381101561036757808201518184015260208101905061034c565b83811115610376576000848401525b50505050565b6000600282049050600182168061039457607f821691505b602082108114156103a8576103a76103df565b5b50919050565b6103b78261043d565b810181811067ffffffffffffffff821117156103d6576103d561040e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fea2646970667358221220264496be069122017e0e6bd1c3b06e335df83ac7d058c0063a81e9227786693564736f6c63430008040033") let abiString = "[{\"constant\":true,\"inputs\":[],\"name\":\"getFlagData\",\"outputs\":[{\"name\":\"data\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"data\",\"type\":\"string\"}],\"name\":\"setFlagData\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - let contractAddress = EthereumAddress("0x811411e3cdfd4750cdd3552feb3b89a46ddb612e") - let web3 = try await Web3.new(LocalTestCase.url) + let receipt = try await deployContract(bytecode: bytecode, abiString: abiString) + let contractAddress = receipt.contractAddress! + let web3 = try await Web3(provider: Web3HttpProvider(url: LocalTestCase.url, network: nil)) let contract = web3.contract(abiString, at: contractAddress, abiVersion: 2) XCTAssert(contract != nil) + let allAddresses = try await web3.eth.ownedAccounts() + let balance = try await web3.eth.getBalance(for: allAddresses[0]) + let writeOperation = contract?.createWriteOperation("setFlagData", parameters: ["abcdefg"]) + writeOperation?.transaction.from = allAddresses[0] + _ = try await writeOperation?.writeToChain(password: "web3swift", policies: Policies(gasLimitPolicy: .manual(3000000)), sendRaw: false) // MARK: Read data from ABI flow // MARK: - Encoding ABI Data flow let tx = contract?.createReadOperation("getFlagData") XCTAssertNotNil(tx) - _ = try await tx!.call() + let result = try await tx!.call() + XCTAssertEqual(result["0"] as? String, "abcdefg") } } diff --git a/Tests/web3swiftTests/localTests/BIP32KeystoreTests.swift b/Tests/web3swiftTests/localTests/BIP32KeystoreTests.swift new file mode 100644 index 000000000..19979ef8c --- /dev/null +++ b/Tests/web3swiftTests/localTests/BIP32KeystoreTests.swift @@ -0,0 +1,62 @@ +// +// BIP32KeystoreTests.swift +// localTests +// +// Created by 6od9i on 29.06.2023. +// + +import Foundation +import XCTest +import Web3Core + +@testable import web3swift + +class BIP32KeystoreTests: XCTestCase { + func testAddressGeneration() throws { + /// Arrange + /// Seed randomly generated for this test + let mnemonic = "resource beyond merit enemy foot piece reveal eagle nothing luggage goose spot" + let password = "test_password" + + let addressesCount: UInt = 101 + + guard let keystore = try BIP32Keystore( + mnemonics: mnemonic, + password: password, + mnemonicsPassword: "", + language: .english, + prefixPath: HDNode.defaultPathMetamaskPrefix) else { + XCTFail("Keystore has not generated") + throw NSError(domain: "0", code: 0) + } + + /// Act + let addresses = try keystore.getAddressForAccount(password: password, + number: addressesCount) + + guard let sameKeystore = try BIP32Keystore( + mnemonics: mnemonic, + password: password, + mnemonicsPassword: "", + language: .english, + prefixPath: HDNode.defaultPathMetamaskPrefix) else { + XCTFail("Keystore has not generated") + throw NSError(domain: "0", code: 0) + } + + let walletNumber = addressesCount - 1 + try sameKeystore.createNewCustomChildAccount(password: password, + path: HDNode.defaultPathMetamaskPrefix + "/\(walletNumber)") + let address = sameKeystore.addresses?.last?.address + + /// Assert + XCTAssertEqual(UInt(addresses.count), addressesCount) + XCTAssertNotEqual(addresses[11], addresses[1]) + XCTAssertEqual(addresses.last?.address, address) + XCTAssertEqual("0xEF22ebb8Bb5CDa4EaCc98b280c94Cbaa3828566F", addresses.last?.address) + XCTAssertEqual("0xdc69CBFE39c46B104875DF9602dFdCDB9b862a16", addresses.first?.address) + XCTAssertEqual("0xdc69CBFE39c46B104875DF9602dFdCDB9b862a16", sameKeystore.addresses?.first?.address) + XCTAssertEqual("0x971CF293b46162CD03DD9Cc39E89B592988DD6C4", addresses[Int(addressesCount / 2)].address) + XCTAssertEqual("0x3B565482a93CE4adA9dE0fD3c118bd41E24CC23C", addresses[10].address) + } +} diff --git a/Tests/web3swiftTests/localTests/EventTests.swift b/Tests/web3swiftTests/localTests/EventTests.swift new file mode 100644 index 000000000..7c1b4187b --- /dev/null +++ b/Tests/web3swiftTests/localTests/EventTests.swift @@ -0,0 +1,90 @@ +// +// EventTests.swift +// +// +// Created by liugang zhang on 2023/8/24. +// + +import XCTest +import Web3Core +import BigInt + +@testable import web3swift + +class EventTests: XCTestCase { + + /// Solidity event allows up to 3 indexed field, this is just for test. + let testEvent = """ + [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"userOpHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"string","name":"a","type":"string"},{"indexed":true,"internalType":"bool","name":"b","type":"bool"},{"indexed":true,"internalType":"bytes","name":"c","type":"bytes"},{"indexed":true,"internalType":"uint256","name":"d","type":"uint256"}],"name":"UserOperationEvent","type":"event"}] + """ + + func testEncodeTopicToJSON() throws { + let encoder = JSONEncoder() + let t1: [EventFilterParameters.Topic] = [] + let t2: [EventFilterParameters.Topic] = [.string(nil)] + let t3: [EventFilterParameters.Topic] = [.strings([.string(nil), .string("1")])] + let t4: [EventFilterParameters.Topic] = [.strings([nil, .string("1")])] + XCTAssertNoThrow(try encoder.encode(t1)) + XCTAssertNoThrow(try encoder.encode(t2)) + XCTAssertNoThrow(try encoder.encode(t3)) + XCTAssertNoThrow(try encoder.encode(t4)) + + let topics: [EventFilterParameters.Topic] = [ + .string("1"), + .strings([ + .string("2"), + .string("3"), + ] + )] + let encoded = try encoder.encode(topics) + let json = try JSONSerialization.jsonObject(with: encoded) + XCTAssertEqual(json as? NSArray, ["1", ["2", "3"]]) + } + + func testEncodeLogs() throws { + let contract = try EthereumContract(testEvent) + let topic = contract.events["UserOperationEvent"]!.topic + let logs = contract.events["UserOperationEvent"]!.encodeParameters( + [ + "0x2c16c07e1c68d502e9c7ad05f0402b365671a0e6517cb807b2de4edd95657042", + "0x581074D2d9e50913eB37665b07CAFa9bFFdd1640", + "hello,world", + true, + "0x02c16c07e1c68d50", + nil + ] + ) + XCTAssertEqual(logs.count, 6) + + XCTAssertTrue(logs[0] == topic.toHexString().addHexPrefix()) + XCTAssertTrue(logs[1] == "0x2c16c07e1c68d502e9c7ad05f0402b365671a0e6517cb807b2de4edd95657042") + XCTAssertTrue(logs[2] == "0x000000000000000000000000581074d2d9e50913eb37665b07cafa9bffdd1640") + XCTAssertTrue(logs[3] == "0xab036729af8b8f9b610af4e11b14fa30c348f40c2c230cce92ef6ef37726fee7") + XCTAssertTrue(logs[4] == "0x0000000000000000000000000000000000000000000000000000000000000001") + XCTAssertTrue(logs[5] == "0x56f5a6cba57d26b32db8dc756fda960dcd3687770a300575a5f8107591eff63f") + } + + func testEncodeTopic() throws { + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .string, indexed: true), value: "hello,world") == "0xab036729af8b8f9b610af4e11b14fa30c348f40c2c230cce92ef6ef37726fee7") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .address, indexed: true), value: "0x003e36550908907c2a2da960fd19a419b9a774b7") == "0x000000000000000000000000003e36550908907c2a2da960fd19a419b9a774b7") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .address, indexed: true), value: EthereumAddress("0x003e36550908907c2a2da960fd19a419b9a774b7")!) == "0x000000000000000000000000003e36550908907c2a2da960fd19a419b9a774b7") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bool, indexed: true), value: true) == "0x0000000000000000000000000000000000000000000000000000000000000001") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bool, indexed: true), value: false) == "0x0000000000000000000000000000000000000000000000000000000000000000") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .uint(bits: 256), indexed: true), value: BigUInt("dbe20a", radix: 16)!) == "0x0000000000000000000000000000000000000000000000000000000000dbe20a") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .uint(bits: 256), indexed: true), value: "dbe20a") == "0x0000000000000000000000000000000000000000000000000000000000dbe20a") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .int(bits: 32), indexed: true), value: 100) == "0x0000000000000000000000000000000000000000000000000000000000000064") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .dynamicBytes, indexed: true), value: Data(hex: "6761766f66796f726b")) == "0xe0859ceea0a2fd2474deef2b2183f10f4c741ebba702e9a07d337522c0af55fb") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bytes(length: 32), indexed: true), value: Data(hex: "6761766f66796f726b")) == "0x00000000000000000000000000000000000000000000006761766f66796f726b") + XCTAssertTrue(ABI.Element.Event.encodeTopic(input: .init(name: "", type: .bytes(length: 32), indexed: true), value: "0x6761766f66796f726b") == "0x00000000000000000000000000000000000000000000006761766f66796f726b") + } +} + +private func ==(lhs: EventFilterParameters.Topic?, rhs: String?) -> Bool { + if let lhs = lhs, case .string(let string) = lhs { + return string == rhs + } + if lhs == nil && rhs == nil { + return true + } + return false +} diff --git a/Tests/web3swiftTests/localTests/LocalTestCase.swift b/Tests/web3swiftTests/localTests/LocalTestCase.swift index 7b79589c4..707fbde93 100644 --- a/Tests/web3swiftTests/localTests/LocalTestCase.swift +++ b/Tests/web3swiftTests/localTests/LocalTestCase.swift @@ -31,4 +31,21 @@ class LocalTestCase: XCTestCase { _ = try! await writeTX.writeToChain(password: "", policies: policies, sendRaw: false) } } + + func deployContract(bytecode: Data, abiString: String) async throws -> TransactionReceipt { + let web3 = try await Web3.new(LocalTestCase.url) + let allAddresses = try await web3.eth.ownedAccounts() + var contract = web3.contract(abiString, at: nil, abiVersion: 2)! + + let parameters: [Any] = [] + // MARK: Writing Data flow + let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! + deployTx.transaction.from = allAddresses[0] + let policies = Policies(gasLimitPolicy: .manual(3000000)) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) + let txHash = result.hash.stripHexPrefix() + Thread.sleep(forTimeInterval: 1.0) + let receipt = try await web3.eth.transactionReceipt(Data.fromHex(txHash)!) + return receipt + } } diff --git a/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift b/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift new file mode 100644 index 000000000..1a6686178 --- /dev/null +++ b/Tests/web3swiftTests/localTests/String+ExtensionTests.swift.swift @@ -0,0 +1,31 @@ +// +// String+ExtensionTests.swift +// +// Created by JeneaVranceanu on 26.11.2023. +// + +import Foundation +import XCTest + +class StringExtensionsTest: XCTestCase { + + func testIsHex() throws { + XCTAssertTrue("0x".isHex) + XCTAssertTrue("0xF".isHex) + XCTAssertTrue("F".isHex) + XCTAssertTrue("0xFF".isHex) + XCTAssertTrue("0x0123456789abcdefABCDEF".isHex) + XCTAssertTrue("0123456789abcdefABCDEF".isHex) + XCTAssertTrue("0123456789abcdefABCDEF ".isHex) + XCTAssertTrue(" 0123456789abcdefABCDEF ".isHex) + XCTAssertTrue(" 0123456789abcdefABCDEF".isHex) + + XCTAssertFalse("".isHex) + XCTAssertFalse("-".isHex) + XCTAssertFalse("xyz".isHex) + XCTAssertFalse("0xCAFEQ".isHex) + XCTAssertFalse("R0123456789abcdefABCDEF ".isHex) + XCTAssertFalse(" R0123456789abcdefABCDEF ".isHex) + } + +} diff --git a/Tests/web3swiftTests/localTests/UncategorizedTests.swift b/Tests/web3swiftTests/localTests/UncategorizedTests.swift index 48b584cba..23a6d0981 100755 --- a/Tests/web3swiftTests/localTests/UncategorizedTests.swift +++ b/Tests/web3swiftTests/localTests/UncategorizedTests.swift @@ -10,7 +10,7 @@ import BigInt @testable import Web3Core @testable import web3swift -class UncategorizedTests: XCTestCase { +class UncategorizedTests: LocalTestCase { func testBitFunctions () throws { let data = Data([0xf0, 0x02, 0x03]) let firstBit = data.bitsInRange(0, 1) @@ -51,6 +51,17 @@ class UncategorizedTests: XCTestCase { XCTAssert(biguint == BigUInt("126978086000000000")) } + func testStringSplit() { + XCTAssertEqual("abcdefgh".split(every: 3), ["abc", "def", "gh"]) + XCTAssertEqual("abcdefgh".split(every: 3, backwards: true), ["ab", "cde", "fgh"]) + + XCTAssertEqual("abcdefgh".split(every: 10), ["abcdefgh"]) + XCTAssertEqual("".split(every: 3), []) + + XCTAssertEqual("abcdefgh".split(every: 1), ["a", "b", "c", "d", "e", "f", "g", "h"]) + XCTAssertEqual("abcdefgh".split(every: 1, backwards: true), ["a", "b", "c", "d", "e", "f", "g", "h"]) // should be the same as from the front + } + func testBloom() throws { let positive = [ "testtest", @@ -109,12 +120,13 @@ class UncategorizedTests: XCTestCase { // } func testPublicMappingsAccess() async throws { + let bytecode = Data(hex: "0x608060405234801561001057600080fd5b5061023d806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063365b98b2146100465780635e79ab6014610076578063bff1f9e1146100a6575b600080fd5b610060600480360381019061005b919061014a565b6100c4565b60405161006d9190610182565b60405180910390f35b610090600480360381019061008b9190610121565b6100ce565b60405161009d9190610182565b60405180910390f35b6100ae6100ee565b6040516100bb9190610182565b60405180910390f35b6000819050919050565b60008173ffffffffffffffffffffffffffffffffffffffff169050919050565b60006064905090565b600081359050610106816101d9565b92915050565b60008135905061011b816101f0565b92915050565b60006020828403121561013357600080fd5b6000610141848285016100f7565b91505092915050565b60006020828403121561015c57600080fd5b600061016a8482850161010c565b91505092915050565b61017c816101cf565b82525050565b60006020820190506101976000830184610173565b92915050565b60006101a8826101af565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6101e28161019d565b81146101ed57600080fd5b50565b6101f9816101cf565b811461020457600080fd5b5056fea26469706673582212207373b0db986284793522a82bff7bf03e30323defa94e6d25f7141e7d63e1ee0564736f6c63430008040033") let jsonString = "[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"users\",\"outputs\":[{\"name\":\"name\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"userDeviceCount\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalUsers\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"}]" + let receipt = try await deployContract(bytecode: bytecode, abiString: jsonString) let web3 = try await Web3.new(LocalTestCase.url) - guard let addr = EthereumAddress("0xdef61132a0c1259464b19e4590e33666aae38574") else {return XCTFail()} - let contract = web3.contract(jsonString, at: addr, abiVersion: 2) - XCTAssert(contract != nil) - let allMethods = contract!.contract.allMethods + guard let addr = receipt.contractAddress else {return XCTFail()} + let contract = web3.contract(jsonString, at: receipt.contractAddress!, abiVersion: 2) + let userDeviceCount = try await contract! .createReadOperation("userDeviceCount", parameters: [addr])? .call() @@ -126,6 +138,10 @@ class UncategorizedTests: XCTestCase { let user = try await contract! .createReadOperation("users", parameters: [0])? .call() + + XCTAssertEqual((userDeviceCount?["0"] as? BigUInt)?.hexString.lowercased(), addr.address.lowercased()) + XCTAssertEqual(totalUsers?["0"] as? BigUInt, 100) + XCTAssertEqual(user?["0"] as? BigUInt, 0) } func testBloomFilterPerformance() throws { diff --git a/Tests/web3swiftTests/localTests/UtilitiesTests.swift b/Tests/web3swiftTests/localTests/UtilitiesTests.swift index 4780805b0..057d99be2 100644 --- a/Tests/web3swiftTests/localTests/UtilitiesTests.swift +++ b/Tests/web3swiftTests/localTests/UtilitiesTests.swift @@ -82,4 +82,9 @@ class UtilitiesTests: XCTestCase { address = Utilities.publicToAddress(Data.fromHex("0x0852972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2")!)?.address XCTAssertEqual(address, nil) } + + func testStringIsHex() { + XCTAssertTrue("1234567890abcdef".isHex) + XCTAssertFalse("ghijklmn".isHex) + } } diff --git a/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift b/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift new file mode 100644 index 000000000..ed5a48bf2 --- /dev/null +++ b/Tests/web3swiftTests/remoteTests/DecodeRemoteErrorTests.swift @@ -0,0 +1,53 @@ +// +// DecodeRemoteErrorTests.swift +// +// Created by liugang zhang on 2023/8/25. +// + +import XCTest +import Web3Core + +@testable import web3swift + +final class DecodeRemoteErrorTests: XCTestCase { + + let entryPoint = EthereumAddress("0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")! + let factory = EthereumAddress("0x9406Cc6185a346906296840746125a0E44976454")! + let address = EthereumAddress("0x581074D2d9e50913eB37665b07CAFa9bFFdd1640")! + + func testDecodeRemoteFunc() async throws { + let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) + + let entryABI = try EthereumContract(abi: [ + .error(.init(name: "SenderAddressResult", + inputs: [.init(name: "sender", type: .address)])), + .function(.init(name: "getSenderAddress", + inputs: [.init(name: "initCode", type: .dynamicBytes)], + outputs: [], + constant: false, + payable: false)) + ], at: entryPoint) + + let factoryABI = try EthereumContract(abi: [ + .function(.init(name: "createAccount", + inputs: [ + .init(name: "owner", type: .address), + .init(name: "salt", type: .uint(bits: 256)) + ], + outputs: [], + constant: false, + payable: false)) + ]) + + let initCode = factory.addressData + factoryABI.method("createAccount", parameters: [address, 0], extraData: nil)! + + do { + try await entryABI.callStatic("getSenderAddress", parameters: [initCode], provider: web3.provider) + XCTFail() + } catch Web3Error.revertCustom(let signature, let args) { + XCTAssertEqual(signature, "SenderAddressResult(address)") + XCTAssertEqual((args["sender"] as? EthereumAddress)?.address, "0x9CF91286f22a1b799770fB5De0E66f3C4cc165d1") + XCTAssertEqual((args["0"] as? EthereumAddress)?.address, "0x9CF91286f22a1b799770fB5De0E66f3C4cc165d1") + } + } +} diff --git a/Tests/web3swiftTests/remoteTests/EventFilterTests.swift b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift new file mode 100644 index 000000000..99a29683b --- /dev/null +++ b/Tests/web3swiftTests/remoteTests/EventFilterTests.swift @@ -0,0 +1,46 @@ +// +// EventFilterTests.swift +// +// +// Created by liugang zhang on 2023/8/24. +// + +import XCTest +import Web3Core +import BigInt +import CryptoSwift +@testable import web3swift + +class EventFilerTests: XCTestCase { + + /// This test tx can be found at here: + /// https://etherscan.io/tx/0x1a1daac5b3158f16399baec9abba2c8a4b4b7ffea5992490079b6bfc4ce70004 + func testErc20Transfer() async throws { + let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) + let address = EthereumAddress("0xdac17f958d2ee523a2206206994597c13d831ec7")! + let erc20 = ERC20(web3: web3, provider: web3.provider, address: address) + + let topics = erc20.contract.contract.event("Transfer", parameters: [ + "0x003e36550908907c2a2da960fd19a419b9a774b7" + ]) + + let parameters = EventFilterParameters(fromBlock: .exact(17983395), toBlock: .exact(17983395), address: [address], topics: topics) + let result = try await web3.eth.getLogs(eventFilter: parameters) + + XCTAssertEqual(result.count, 1) + + let log = result.first! + XCTAssertEqual(log.address.address.lowercased(), "0xdac17f958d2ee523a2206206994597c13d831ec7") + XCTAssertEqual(log.transactionHash.toHexString().lowercased(), "1a1daac5b3158f16399baec9abba2c8a4b4b7ffea5992490079b6bfc4ce70004") + + let logTopics = log.topics.map { $0.toHexString() } + topics.compactMap { t -> String? in + if let t = t, case EventFilterParameters.Topic.string(let topic) = t { + return topic + } + return nil + }.forEach { t in + XCTAssertTrue(logTopics.contains(t.stripHexPrefix())) + } + } +} diff --git a/Tests/web3swiftTests/remoteTests/InfuraTests.swift b/Tests/web3swiftTests/remoteTests/InfuraTests.swift index fec9289ef..da1cdf412 100755 --- a/Tests/web3swiftTests/remoteTests/InfuraTests.swift +++ b/Tests/web3swiftTests/remoteTests/InfuraTests.swift @@ -10,7 +10,6 @@ import Web3Core // MARK: Works only with network connection class InfuraTests: XCTestCase { - func testGetBalance() async throws { let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken) let address = EthereumAddress("0xd61b5ca425F8C8775882d4defefC68A6979DBbce")! diff --git a/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift b/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift new file mode 100644 index 000000000..b2a5e144b --- /dev/null +++ b/Tests/web3swiftTests/remoteTests/Web3HttpProviderTests.swift @@ -0,0 +1,33 @@ +// +// Web3HttpProviderTests.swift +// +// +// Created by liugang zhang on 2023/9/2. +// + +import XCTest +import Web3Core + +@testable import web3swift + +final class Web3HttpProviderTests: XCTestCase { + + /// if one of these rpc server lose efficacy, find a substitution from https://chainlist.org/ + func testGetNetwork() async throws { + let requestURLstring = "https://" + Networks.Mainnet.name + Constants.infuraHttpScheme + Constants.infuraToken + var web3 = try await Web3HttpProvider(url: URL(string: requestURLstring)!, network: nil) + XCTAssertEqual(web3.network?.chainID, 1) + + web3 = try await Web3HttpProvider(url: URL(string: "https://arbitrum-one.publicnode.com")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 42161) + + web3 = try await Web3HttpProvider(url: URL(string: "https://rpc.ankr.com/bsc")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 56) + + web3 = try await Web3HttpProvider(url: URL(string: "https://rpc-mainnet.maticvigil.com/")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 137) + + web3 = try await Web3HttpProvider(url: URL(string: "https://optimism.gateway.tenderly.co")!, network: nil) + XCTAssertEqual(web3.network?.chainID, 10) + } +}