From 0929284f2e3c484dfdc8bb6c3f671ca1e654c0b0 Mon Sep 17 00:00:00 2001 From: Alex Vlasov Date: Tue, 26 Dec 2017 21:39:33 +0300 Subject: [PATCH] parse event logs --- .../web3swiftExample/ViewController.swift | 8 +- README.md | 4 +- web3swift.podspec | 2 +- web3swift/ABI/Classes/ABIDecoder.swift | 75 +++++++++++++++++++ web3swift/ABI/Classes/ABITypes.swift | 15 ++++ web3swift/Contract/Classes/Contract.swift | 24 ++++++ .../Contract/Classes/Web3+Contract.swift | 4 + .../Contract/Classes/Web3+Structures.swift | 42 +++++++++-- 8 files changed, 163 insertions(+), 11 deletions(-) diff --git a/Example/web3swiftExample/web3swiftExample/ViewController.swift b/Example/web3swiftExample/web3swiftExample/ViewController.swift index 9e032dc2..fa6f56bc 100644 --- a/Example/web3swiftExample/web3swiftExample/ViewController.swift +++ b/Example/web3swiftExample/web3swiftExample/ViewController.swift @@ -49,7 +49,13 @@ class ViewController: UIViewController { let result = try await((intermediate?.call(options: options))!) print("BKX token name = " + (result!["0"] as! String)) - + let erc20receipt = try await(web3Main.eth.getTransactionReceipt("0x76bb19c0b7e2590f724871960599d28db99cd587506fdfea94062f9c8d61eb30")) + for l in (erc20receipt?.logs)! { + guard let result = contract?.parseEvent(l), let name = result.eventName, let data = result.eventData else {continue} + print("Parsed event " + name) + print("Parsed content") + print(data) + } // Block number on Main let blockNumber = try await(web3Main.eth.getBlockNumber()) diff --git a/README.md b/README.md index 6191ef89..8050473c 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - Serialize and deserialize transactions and results to native Swift types - Convenience functions for chain state: block number, gas price - Check transaction results and get receipt -- WIP: Parse event logs for transaction +- Parse event logs for transaction ## Example @@ -40,4 +40,4 @@ Petr Korolev, @skywinder, pk@bankexfoundation.org ## License -web3swift is available under the MIT license. See the LICENSE file for more info. \ No newline at end of file +web3swift is available under the MIT license. See the LICENSE file for more info. diff --git a/web3swift.podspec b/web3swift.podspec index 560f1362..2921e8bd 100644 --- a/web3swift.podspec +++ b/web3swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "web3swift" -s.version = "0.1.0" +s.version = "0.1.1" s.summary = "Web3 implementation in vanilla Swift" s.description = <<-DESC diff --git a/web3swift/ABI/Classes/ABIDecoder.swift b/web3swift/ABI/Classes/ABIDecoder.swift index 50546acf..aa174a3f 100644 --- a/web3swift/ABI/Classes/ABIDecoder.swift +++ b/web3swift/ABI/Classes/ABIDecoder.swift @@ -193,3 +193,78 @@ extension ABIElement { } } } + +extension ABIElement { + func decodeReturnedLogs(_ eventLog: EventLog) -> [String:Any]? { + switch self { + case .constructor(_): + return nil + case .event(let event): + if event.anonymous {return nil} + if eventLog.topics[0] != event.topic { + return nil + } + var eventContent = [String: Any]() + eventContent["name"]=event.name + let logs = eventLog.topics + var j = 1 + for i in 0 ..< event.inputs.count { + let el = event.inputs[i] + if el.indexed { + let elementData = logs[j] + j = j + 1 + let expectedType = el.type + switch expectedType { + case .staticType(let type): + let decoded = type.decode(expectedType: type, data: elementData, tailPointer: BigUInt(0)) + guard let value = decoded.value, let _ = decoded.bytesConsumed else {break} + let name = "\(i)" + eventContent[name] = value + if el.name != "" { + eventContent[el.name] = value + } + case .dynamicType(let type): + let decoded = type.decode(expectedType: type, data: elementData, tailPointer: BigUInt(0)) + guard let value = decoded.value, let _ = decoded.bytesConsumed else {break} + let name = "\(i)" + eventContent[name] = value + if el.name != "" { + eventContent[el.name] = value + } + } + } else { + var dataForProcessing = eventLog.data + var tailPointer = BigUInt(0) + let expectedType = el.type + switch expectedType { + case .staticType(let type): + let decoded = type.decode(expectedType: type, data: dataForProcessing, tailPointer: BigUInt(0)) + guard let value = decoded.value, let consumed = decoded.bytesConsumed else {break} + let name = "\(i)" + eventContent[name] = value + if el.name != "" { + eventContent[el.name] = value + } + dataForProcessing = Data(dataForProcessing[consumed...]) + tailPointer = tailPointer + BigUInt(consumed) + case .dynamicType(let type): + let decoded = type.decode(expectedType: type, data: dataForProcessing, tailPointer: tailPointer) + guard let value = decoded.value, let consumed = decoded.bytesConsumed else {break} + let name = "\(i)" + eventContent[name] = value + if el.name != "" { + eventContent[el.name] = value + } + dataForProcessing = Data(dataForProcessing[consumed...]) + tailPointer = tailPointer + BigUInt(consumed) + } + } + } + return eventContent + case .fallback(_): + return nil + case .function(_): + return nil + } + } +} diff --git a/web3swift/ABI/Classes/ABITypes.swift b/web3swift/ABI/Classes/ABITypes.swift index 8de6187e..47556514 100644 --- a/web3swift/ABI/Classes/ABITypes.swift +++ b/web3swift/ABI/Classes/ABITypes.swift @@ -31,6 +31,9 @@ public struct ABIRecord: Decodable { var anonymous: Bool? } +public struct EventLogJSON { + +} // Native parsing @@ -233,6 +236,17 @@ extension ABIElement.Function { } } +// MARK: - Event topic +extension ABIElement.Event { + public var signature: String { + return "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))" + } + + public var topic: Data { + return signature.data(using: .ascii)!.sha3(.keccak256) + } +} + protocol AbiEncoding { var abiRepresentation: String { get } } @@ -284,3 +298,4 @@ extension ABIElement.ParameterType.DynamicType: AbiEncoding { } } } + diff --git a/web3swift/Contract/Classes/Contract.swift b/web3swift/Contract/Classes/Contract.swift index 42198ba5..3713aa94 100644 --- a/web3swift/Contract/Classes/Contract.swift +++ b/web3swift/Contract/Classes/Contract.swift @@ -25,6 +25,20 @@ public struct Contract { } return toReturn } + var events: [String: ABIElement] { + var toReturn = [String: ABIElement]() + for m in self._abi { + switch m { + case .event(let event): + let name = event.name + toReturn[name] = m + default: + continue + } + } + return toReturn + } + var options: Web3Options? = Web3Options.defaultOptions() var chainID: BigUInt = BigUInt(1) @@ -83,4 +97,14 @@ public struct Contract { let transaction = EthereumTransaction(nonce: nonce, gasPrice: gasPrice, gasLimit: gas, to: to, value: value, data: encodedData, v: chainID, r: BigUInt(0), s: BigUInt(0)) return transaction } + + public func parseEvent(_ eventLog: EventLog) -> (eventName:String?, eventData:[String:Any]?) { + for (eName, ev) in self.events { + let parsed = ev.decodeReturnedLogs(eventLog) + if parsed != nil { + return (eName, parsed!) + } + } + return (nil, nil) + } } diff --git a/web3swift/Contract/Classes/Web3+Contract.swift b/web3swift/Contract/Classes/Web3+Contract.swift index 155e3a25..984a7940 100644 --- a/web3swift/Contract/Classes/Web3+Contract.swift +++ b/web3swift/Contract/Classes/Web3+Contract.swift @@ -192,5 +192,9 @@ extension web3 { let intermediate = transactionIntermediate(transaction: tx, web3: self.web3, contract: self.contract, method: method, options: mergedOptions) return intermediate } + + public func parseEvent(_ eventLog: EventLog) -> (eventName:String?, eventData:[String:Any]?) { + return self.contract.parseEvent(eventLog) + } } } diff --git a/web3swift/Contract/Classes/Web3+Structures.swift b/web3swift/Contract/Classes/Web3+Structures.swift index 514d9f6b..b60fad80 100644 --- a/web3swift/Contract/Classes/Web3+Structures.swift +++ b/web3swift/Contract/Classes/Web3+Structures.swift @@ -65,7 +65,7 @@ public struct TransactionReceipt { public var contractAddress: EthereumAddress? public var cumulativeGasUsed: BigUInt public var gasUsed: BigUInt - public var logs: [Data] = [Data]() + public var logs: [EventLog] public var status: TXStatus public enum TXStatus { @@ -81,7 +81,7 @@ public struct TransactionReceipt { let ca = json["contractAddress"] as? String guard let cgu = json["cumulativeGasUsed"] as? String else {return nil} guard let gu = json["gasUsed"] as? String else {return nil} -// guard let ls = json["logs"] as? [String] else {return nil} + guard let ls = json["logs"] as? Array<[String:Any]> else {return nil} guard let st = json["status"] as? String else {return nil} transactionHash = h @@ -97,11 +97,11 @@ public struct TransactionReceipt { cumulativeGasUsed = cguUnwrapped guard let guUnwrapped = BigUInt(gu.stripHexPrefix(), radix: 16) else {return nil} gasUsed = guUnwrapped - var allLogs = [Data]() -// for l in ls { -// let logData = Data(Array(hex: l.lowercased().stripHexPrefix())) -// allLogs.append(logData) -// } + var allLogs = [EventLog]() + for l in ls { + guard let log = EventLog(l) else {return nil} + allLogs.append(log) + } logs = allLogs if st == "0x1" { status = TXStatus.ok @@ -111,3 +111,31 @@ public struct TransactionReceipt { } } +public struct EventLog { + public var address: EthereumAddress + public var data: Data + public var logIndex: BigUInt + public var removed: Bool + public var topics: [Data] + + public init? (_ json: [String: Any]) { + guard let ad = json["address"] as? String else {return nil} + guard let d = json["data"] as? String else {return nil} + guard let li = json["logIndex"] as? String else {return nil} + guard let rm = json["removed"] as? Int else {return nil} + guard let tpc = json["topics"] as? [String] else {return nil} + address = EthereumAddress(ad) + data = Data(Array(hex: d.lowercased().stripHexPrefix())) + + guard let liUnwrapped = BigUInt(li.stripHexPrefix(), radix: 16) else {return nil} + logIndex = liUnwrapped + removed = rm == 1 ? true : false + var tops = [Data]() + for t in tpc { + let topic = Data(Array(hex: t.lowercased().stripHexPrefix())) + tops.append(topic) + } + topics = tops + } +} +