Skip to content

Commit 7719379

Browse files
committed
feat: enable encode Event and IETH.getLogs
1 parent dab2667 commit 7719379

File tree

7 files changed

+210
-3
lines changed

7 files changed

+210
-3
lines changed

Sources/Web3Core/Contract/ContractProtocol.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,13 @@ extension DefaultContractProtocol {
280280
return encodedData
281281
}
282282

283+
public func event(_ event: String, parameters: [Any]) -> [EventFilterParameters.Topic?] {
284+
guard let event = events[event] else {
285+
return []
286+
}
287+
return event.encodeParameters(parameters)
288+
}
289+
283290
public func parseEvent(_ eventLog: EventLog) -> (eventName: String?, eventData: [String: Any]?) {
284291
for (eName, ev) in self.events {
285292
if !ev.anonymous {

Sources/Web3Core/EthereumABI/ABIElements.swift

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,67 @@ extension ABI.Element.Function {
211211
}
212212
}
213213

214-
// MARK: - Event logs decoding
214+
// MARK: - Event logs decoding & encoding
215215

216216
extension ABI.Element.Event {
217217
public func decodeReturnedLogs(eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? {
218218
guard let eventContent = ABIDecoder.decodeLog(event: self, eventLogTopics: eventLogTopics, eventLogData: eventLogData) else { return nil }
219219
return eventContent
220220
}
221+
222+
func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? {
223+
if case .string = input.type {
224+
guard let string = value as? String else {
225+
return nil
226+
}
227+
return .string(string.sha3(.keccak256).addHexPrefix())
228+
} else if case .dynamicBytes = input.type {
229+
guard let data = ABIEncoder.convertToData(value) else {
230+
return nil
231+
}
232+
return .string(data.sha3(.keccak256).toHexString().addHexPrefix())
233+
} else if case .address = input.type {
234+
guard let encoded = ABIEncoder.encode(types: [input.type], values: [value]) else {
235+
return nil
236+
}
237+
return .string(encoded.toHexString().addHexPrefix())
238+
}
239+
guard let data = ABIEncoder.convertToData(value)!.setLengthLeft(32) else {
240+
return nil
241+
}
242+
return .string(data.toHexString().addHexPrefix())
243+
}
244+
245+
public func encodeParameters(_ parameters: [Any?]) -> [EventFilterParameters.Topic?] {
246+
guard parameters.count <= inputs.count else {
247+
// too many arguments for fragment
248+
return []
249+
}
250+
var topics: [EventFilterParameters.Topic?] = []
251+
252+
if !anonymous {
253+
topics.append(.string(topic.toHexString().addHexPrefix()))
254+
}
255+
256+
for (i, p) in parameters.enumerated() {
257+
let input = inputs[i]
258+
if !input.indexed {
259+
// cannot filter non-indexed parameters; must be null
260+
return []
261+
}
262+
if p == nil {
263+
topics.append(.string(nil))
264+
} else if input.type.isArray {
265+
// filtering with tuples or arrays not supported
266+
return []
267+
} else if let p = p as? Array<Any> {
268+
topics.append(.strings(p.map { encodeTopic(input: input, value: $0) }))
269+
} else {
270+
topics.append(encodeTopic(input: input, value: p!))
271+
}
272+
}
273+
return topics
274+
}
221275
}
222276

223277
// MARK: - Function input/output decoding

Sources/Web3Core/Transaction/EventfilterParameters.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ extension EventFilterParameters {
5050
var container = encoder.container(keyedBy: CodingKeys.self)
5151
try container.encode(fromBlock.description, forKey: .fromBlock)
5252
try container.encode(toBlock.description, forKey: .toBlock)
53-
try container.encode(address.description, forKey: .address)
54-
try container.encode(topics.textRepresentation, forKey: .topics)
53+
try container.encode(address, forKey: .address)
54+
try container.encode(topics, forKey: .topics)
5555
}
5656
}
5757

@@ -96,6 +96,17 @@ extension EventFilterParameters {
9696
case string(String?)
9797
case strings([Topic?]?)
9898

99+
public func encode(to encoder: Encoder) throws {
100+
switch self {
101+
case let .string(s):
102+
var container = encoder.singleValueContainer()
103+
try container.encode(s)
104+
case let .strings(ss):
105+
var container = encoder.unkeyedContainer()
106+
try container.encode(contentsOf: ss ?? [])
107+
}
108+
}
109+
99110
var rawValue: String {
100111
switch self {
101112
case let .string(string):

Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ public extension IEth {
130130
}
131131
}
132132

133+
public extension IEth {
134+
func getLogs(eventFilter: EventFilterParameters) async throws -> [EventLog] {
135+
try await APIRequest.sendRequest(with: self.provider, for: .getLogs(eventFilter)).result
136+
}
137+
}
138+
133139
public extension IEth {
134140
func send(_ transaction: CodableTransaction) async throws -> TransactionSendingResult {
135141
let request = APIRequest.sendTransaction(transaction)

Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public protocol IEth {
2525

2626
func code(for address: EthereumAddress, onBlock: BlockNumber) async throws -> Hash
2727

28+
func getLogs(eventFilter: EventFilterParameters) async throws -> [EventLog]
29+
2830
func gasPrice() async throws -> BigUInt
2931

3032
func getTransactionCount(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//
2+
// EventTests.swift
3+
//
4+
//
5+
// Created by liugang zhang on 2023/8/24.
6+
//
7+
8+
import XCTest
9+
import Web3Core
10+
11+
@testable import web3swift
12+
13+
class EventTests: XCTestCase {
14+
func testEncodeTopc() throws {
15+
let encoder = JSONEncoder()
16+
let t1: [EventFilterParameters.Topic] = []
17+
let t2: [EventFilterParameters.Topic] = [.string(nil)]
18+
let t3: [EventFilterParameters.Topic] = [.strings([.string(nil), .string("1")])]
19+
XCTAssertNoThrow(try encoder.encode(t1))
20+
XCTAssertNoThrow(try encoder.encode(t2))
21+
XCTAssertNoThrow(try encoder.encode(t3))
22+
23+
let t4: [EventFilterParameters.Topic] = [
24+
.string("1"),
25+
.strings([
26+
.string("2"),
27+
.string("3"),
28+
]
29+
)]
30+
let encoded = try encoder.encode(t4)
31+
let json = try JSONSerialization.jsonObject(with: encoded)
32+
XCTAssertEqual(json as? NSArray, ["1", ["2", "3"]])
33+
}
34+
35+
func testEncodeLogs() throws {
36+
let contract = try EthereumContract(TestEvent)
37+
let logs = contract.events["UserOperationEvent"]?.encodeParameters(
38+
[
39+
"0x2c16c07e1c68d502e9c7ad05f0402b365671a0e6517cb807b2de4edd95657042",
40+
"0x581074D2d9e50913eB37665b07CAFa9bFFdd1640",
41+
"hello,world",
42+
true,
43+
"0x02c16c07e1c68d50",
44+
nil
45+
]
46+
)
47+
48+
XCTAssert(logs?.count == 7)
49+
}
50+
51+
let TestEvent = """
52+
[{
53+
"anonymous": false,
54+
"inputs": [
55+
{
56+
"indexed": true,
57+
"internalType": "bytes32",
58+
"name": "userOpHash",
59+
"type": "bytes32"
60+
},
61+
{
62+
"indexed": true,
63+
"internalType": "address",
64+
"name": "sender",
65+
"type": "address"
66+
},
67+
{
68+
"indexed": true,
69+
"internalType": "string",
70+
"name": "a",
71+
"type": "string"
72+
},
73+
{
74+
"indexed": true,
75+
"internalType": "bool",
76+
"name": "b",
77+
"type": "bool"
78+
},
79+
{
80+
"indexed": true,
81+
"internalType": "bytes",
82+
"name": "c",
83+
"type": "bytes"
84+
},
85+
{
86+
"indexed": true,
87+
"internalType": "uint256",
88+
"name": "d",
89+
"type": "uint256"
90+
},
91+
],
92+
"name": "UserOperationEvent",
93+
"type": "event"
94+
}
95+
]
96+
"""
97+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// EventFilterTests.swift
3+
//
4+
//
5+
// Created by liugang zhang on 2023/8/24.
6+
//
7+
8+
import XCTest
9+
import Web3Core
10+
11+
@testable import web3swift
12+
13+
class EventFilerTests: XCTestCase {
14+
15+
func testErc20Transfer() async throws {
16+
let web3 = try await Web3.InfuraMainnetWeb3(accessToken: Constants.infuraToken)
17+
let address = EthereumAddress("0xdac17f958d2ee523a2206206994597c13d831ec7")!
18+
let erc20 = ERC20(web3: web3, provider: web3.provider, address: address)
19+
20+
let topics = erc20.contract.contract.event("Transfer", parameters: [
21+
"0x003e36550908907c2a2da960fd19a419b9a774b7"
22+
])
23+
let block = try await web3.eth.block(by: .latest)
24+
let parameters = EventFilterParameters(fromBlock: .exact(block.number - 1000), address: [address], topics: topics)
25+
let result = try await web3.eth.getLogs(eventFilter: parameters)
26+
27+
// result not always has object in it.
28+
print(result)
29+
}
30+
}

0 commit comments

Comments
 (0)