Skip to content

Commit 6fa5667

Browse files
Merge branch 'develop' into chore/more-sign-tests
2 parents 13181b4 + bab86ca commit 6fa5667

File tree

15 files changed

+412
-69
lines changed

15 files changed

+412
-69
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
- [x]**Literally following the standards** (BIP, EIP, etc):
5959
- [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)**
6060
- [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)
61-
- [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
61+
- [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
6262
- [x] **RLP encoding**
6363
- [x] Base58 encoding scheme
6464
- [x] Formatting to and from Ethereum Units

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: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,85 @@ 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+
public static func encodeTopic(input: ABI.Element.Event.Input, value: Any) -> EventFilterParameters.Topic? {
223+
switch input.type {
224+
case .string:
225+
guard let string = value as? String else {
226+
return nil
227+
}
228+
return .string(string.sha3(.keccak256).addHexPrefix())
229+
case .dynamicBytes:
230+
guard let data = ABIEncoder.convertToData(value) else {
231+
return nil
232+
}
233+
return .string(data.sha3(.keccak256).toHexString().addHexPrefix())
234+
case .bytes(length: _):
235+
guard let data = ABIEncoder.convertToData(value), let data = data.setLengthLeft(32) else {
236+
return nil
237+
}
238+
return .string(data.toHexString().addHexPrefix())
239+
case .address, .uint(bits: _), .int(bits: _), .bool:
240+
guard let encoded = ABIEncoder.encodeSingleType(type: input.type, value: value) else {
241+
return nil
242+
}
243+
return .string(encoded.toHexString().addHexPrefix())
244+
default:
245+
guard let data = try? ABIEncoder.abiEncode(value).setLengthLeft(32) else {
246+
return nil
247+
}
248+
return .string(data.toHexString().addHexPrefix())
249+
}
250+
}
251+
252+
public func encodeParameters(_ parameters: [Any?]) -> [EventFilterParameters.Topic?] {
253+
guard parameters.count <= inputs.count else {
254+
// too many arguments for fragment
255+
return []
256+
}
257+
var topics: [EventFilterParameters.Topic?] = []
258+
259+
if !anonymous {
260+
topics.append(.string(topic.toHexString().addHexPrefix()))
261+
}
262+
263+
for (i, p) in parameters.enumerated() {
264+
let input = inputs[i]
265+
if !input.indexed {
266+
// cannot filter non-indexed parameters; must be null
267+
return []
268+
}
269+
if p == nil {
270+
topics.append(nil)
271+
} else if input.type.isArray || input.type.isTuple {
272+
// filtering with tuples or arrays not supported
273+
return []
274+
} else if let p = p as? Array<Any> {
275+
topics.append(.strings(p.map { Self.encodeTopic(input: input, value: $0) }))
276+
} else {
277+
topics.append(Self.encodeTopic(input: input, value: p!))
278+
}
279+
}
280+
281+
// Trim off trailing nulls
282+
while let last = topics.last {
283+
if last == nil {
284+
topics.removeLast()
285+
} else if case .string(let string) = last, string == nil {
286+
topics.removeLast()
287+
} else {
288+
break
289+
}
290+
}
291+
return topics
292+
}
221293
}
222294

223295
// MARK: - Function input/output decoding

Sources/Web3Core/EthereumNetwork/Utility/HexDecodable+Extensions.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@ extension BigInt: LiteralInitiableFromString { }
1717
extension BigUInt: LiteralInitiableFromString { }
1818

1919
extension Data: LiteralInitiableFromString {
20+
/// Converts hexadecimal string representation of some bytes into actual bytes.
21+
/// Notes:
22+
/// - empty string will return `nil`;
23+
/// - empty hex string, meaning it's equal to `"0x"`, will return empty `Data` object.
24+
/// - Parameter hex: bytes represented as string.
25+
/// - Returns: optional raw bytes.
2026
public static func fromHex(_ hex: String) -> Data? {
21-
let string = hex.lowercased().stripHexPrefix()
22-
let array = [UInt8](hex: string)
23-
if array.count == 0 {
24-
if hex == "0x" || hex == "" {
25-
return Data()
26-
} else {
27-
return nil
28-
}
29-
}
30-
return Data(array)
27+
let hex = hex.lowercased().trim()
28+
guard !hex.isEmpty else { return nil }
29+
guard hex != "0x" else { return Data() }
30+
let bytes = [UInt8](hex: hex.stripHexPrefix())
31+
return bytes.isEmpty ? nil : Data(bytes)
3132
}
3233
}

Sources/Web3Core/KeystoreManager/BIP32Keystore.swift

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -149,71 +149,76 @@ public class BIP32Keystore: AbstractKeystore {
149149
} else {
150150
newIndex = UInt32.zero
151151
}
152+
152153
guard let newNode = parentNode.derive(index: newIndex, derivePrivateKey: true, hardened: false) else {
153154
throw AbstractKeystoreError.keyDerivationError
154155
}
155156
guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else {
156157
throw AbstractKeystoreError.keyDerivationError
157158
}
158-
let prefixPath = self.rootPrefix
159-
var newPath: String
160-
if newNode.isHardened {
161-
newPath = prefixPath + "/" + String(newNode.index % HDNode.hardenedIndexPrefix) + "'"
162-
} else {
163-
newPath = prefixPath + "/" + String(newNode.index)
164-
}
159+
let newPath = rootPrefix + "/" + String(newNode.index)
165160
addressStorage.add(address: newAddress, for: newPath)
166161
}
167162

168163
public func createNewCustomChildAccount(password: String, path: String) throws {
169-
guard let decryptedRootNode = try getPrefixNodeData(password) else {
164+
guard let decryptedRootNode = try getPrefixNodeData(password),
165+
let keystoreParams else {
170166
throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")
171167
}
172168
guard let rootNode = HDNode(decryptedRootNode) else {
173169
throw AbstractKeystoreError.encryptionError("Failed to deserialize a root node")
174170
}
175-
let prefixPath = self.rootPrefix
176-
var pathAppendix: String?
171+
172+
let prefixPath = rootPrefix
173+
var pathAppendix = path
174+
177175
if path.hasPrefix(prefixPath) {
178-
let upperIndex = (path.range(of: prefixPath)?.upperBound)!
179-
if upperIndex < path.endIndex {
180-
pathAppendix = String(path[path.index(after: upperIndex)])
176+
if let upperIndex = (path.range(of: prefixPath)?.upperBound), upperIndex < path.endIndex {
177+
pathAppendix = String(path[path.index(after: upperIndex)..<path.endIndex])
181178
} else {
182179
throw AbstractKeystoreError.encryptionError("out of bounds")
183180
}
184-
185-
guard pathAppendix != nil else {
186-
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
187-
}
188-
if pathAppendix!.hasPrefix("/") {
189-
pathAppendix = pathAppendix?.trimmingCharacters(in: CharacterSet.init(charactersIn: "/"))
190-
}
191-
} else {
192-
if path.hasPrefix("/") {
193-
pathAppendix = path.trimmingCharacters(in: CharacterSet.init(charactersIn: "/"))
194-
}
195181
}
196-
guard pathAppendix != nil else {
197-
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
182+
if pathAppendix.hasPrefix("/") {
183+
pathAppendix = pathAppendix.trimmingCharacters(in: .init(charactersIn: "/"))
198184
}
199185
guard rootNode.depth == prefixPath.components(separatedBy: "/").count - 1 else {
200186
throw AbstractKeystoreError.encryptionError("Derivation depth mismatch")
201187
}
202-
guard let newNode = rootNode.derive(path: pathAppendix!, derivePrivateKey: true) else {
188+
guard let newNode = rootNode.derive(path: pathAppendix, derivePrivateKey: true) else {
203189
throw AbstractKeystoreError.keyDerivationError
204190
}
205191
guard let newAddress = Utilities.publicToAddress(newNode.publicKey) else {
206192
throw AbstractKeystoreError.keyDerivationError
207193
}
208-
var newPath: String
209-
if newNode.isHardened {
210-
newPath = prefixPath + "/" + pathAppendix!.trimmingCharacters(in: CharacterSet.init(charactersIn: "'")) + "'"
211-
} else {
212-
newPath = prefixPath + "/" + pathAppendix!
213-
}
194+
195+
let newPath = prefixPath + "/" + pathAppendix
196+
214197
addressStorage.add(address: newAddress, for: newPath)
215-
guard let serializedRootNode = rootNode.serialize(serializePublic: false) else {throw AbstractKeystoreError.keyDerivationError}
216-
try encryptDataToStorage(password, data: serializedRootNode, aesMode: self.keystoreParams!.crypto.cipher)
198+
guard let serializedRootNode = rootNode.serialize(serializePublic: false) else {
199+
throw AbstractKeystoreError.keyDerivationError
200+
}
201+
try encryptDataToStorage(password, data: serializedRootNode, aesMode: keystoreParams.crypto.cipher)
202+
}
203+
204+
/// Fast generation addresses for current account
205+
/// used to show which addresses the user can get for indices from `0` to `number-1`
206+
/// - Parameters:
207+
/// - password: password of seed storage
208+
/// - number: number of wallets addresses needed to generate from `0` to `number-1`
209+
/// - Returns: Array of addresses generated from `0` to number bound
210+
public func getAddressForAccount(password: String, number: UInt) throws -> [EthereumAddress] {
211+
guard let decryptedRootNode = try? getPrefixNodeData(password),
212+
let rootNode = HDNode(decryptedRootNode) else {
213+
throw AbstractKeystoreError.encryptionError("Failed to decrypt a keystore")
214+
}
215+
return try [UInt](0..<number).compactMap { number in
216+
guard rootNode.depth == rootPrefix.components(separatedBy: "/").count - 1,
217+
let newNode = rootNode.derive(path: "\(number)", derivePrivateKey: true) else {
218+
throw AbstractKeystoreError.keyDerivationError
219+
}
220+
return Utilities.publicToAddress(newNode.publicKey)
221+
}
217222
}
218223

219224
fileprivate func encryptDataToStorage(_ password: String, data: Data, dkLen: Int = 32, N: Int = 4096, R: Int = 6, P: Int = 1, aesMode: String = "aes-128-cbc") throws {

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/Web3Core/Utility/Data+Extension.swift

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
import Foundation
77

8-
extension Data {
8+
public extension Data {
9+
910
init<T>(fromArray values: [T]) {
1011
let values = values
1112
let ptrUB = values.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer) in return ptr }
@@ -33,32 +34,34 @@ extension Data {
3334
return difference == UInt8(0x00)
3435
}
3536

36-
public static func zero(_ data: inout Data) {
37+
static func zero(_ data: inout Data) {
3738
let count = data.count
3839
data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
3940
body.baseAddress?.assumingMemoryBound(to: UInt8.self).initialize(repeating: 0, count: count)
4041
}
4142
}
4243

43-
public static func randomBytes(length: Int) -> Data? {
44-
for _ in 0...1024 {
45-
var data = Data(repeating: 0, count: length)
46-
let result = data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) -> Int32? in
47-
if let bodyAddress = body.baseAddress, body.count > 0 {
48-
let pointer = bodyAddress.assumingMemoryBound(to: UInt8.self)
49-
return SecRandomCopyBytes(kSecRandomDefault, length, pointer)
50-
} else {
51-
return nil
52-
}
53-
}
54-
if let notNilResult = result, notNilResult == errSecSuccess {
55-
return data
56-
}
44+
/**
45+
Generates an array of random bytes of the specified length.
46+
This function uses `SecRandomCopyBytes` to generate random bytes returning it as a `Data` object.
47+
If an error occurs during random bytes generation, the function returns `nil`.
48+
Error occurs only if `SecRandomCopyBytes` returns status that is not `errSecSuccess`.
49+
See [all status codes](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes) for possible error reasons.
50+
Note: in v4 of web3swift this function will be deprecated and a new implementation will be provided that will throw occurred error.
51+
- Parameter length: The number of random bytes to generate.
52+
53+
- Returns: optional `Data` object containing the generated random bytes, or `nil` if an error occurred during generation.
54+
*/
55+
static func randomBytes(length: Int) -> Data? {
56+
var entropyBytes = [UInt8](repeating: 0, count: length)
57+
let status = SecRandomCopyBytes(kSecRandomDefault, entropyBytes.count, &entropyBytes)
58+
guard status == errSecSuccess else {
59+
return nil
5760
}
58-
return nil
61+
return Data(entropyBytes)
5962
}
6063

61-
public func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public
64+
func bitsInRange(_ startingBit: Int, _ length: Int) -> UInt64? { // return max of 8 bytes for simplicity, non-public
6265
if startingBit + length / 8 > self.count, length > 64, startingBit > 0, length >= 1 { return nil }
6366
let bytes = self[(startingBit/8) ..< (startingBit+length+7)/8]
6467
let padding = Data(repeating: 0, count: 8 - bytes.count)

Sources/Web3Core/Utility/String+Extension.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ extension String {
135135
func trim() -> String {
136136
trimmingCharacters(in: .whitespacesAndNewlines)
137137
}
138+
139+
/// Splits a string into groups of `every` n characters, grouping from left-to-right by default. If `backwards` is true, right-to-left.
140+
public func split(every: Int, backwards: Bool = false) -> [String] {
141+
var result = [String]()
142+
143+
for i in stride(from: 0, to: self.count, by: every) {
144+
switch backwards {
145+
case true:
146+
let endIndex = self.index(self.endIndex, offsetBy: -i)
147+
let startIndex = self.index(endIndex, offsetBy: -every, limitedBy: self.startIndex) ?? self.startIndex
148+
result.insert(String(self[startIndex..<endIndex]), at: 0)
149+
case false:
150+
let startIndex = self.index(self.startIndex, offsetBy: i)
151+
let endIndex = self.index(startIndex, offsetBy: every, limitedBy: self.endIndex) ?? self.endIndex
152+
result.append(String(self[startIndex..<endIndex]))
153+
}
154+
}
155+
156+
return result
157+
}
138158
}
139159

140160
extension Character {

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

0 commit comments

Comments
 (0)