Skip to content

Commit 1ae35e6

Browse files
fix: EIP681 - support for parsing arrays in query parameters
1 parent fddc581 commit 1ae35e6

File tree

2 files changed

+463
-83
lines changed

2 files changed

+463
-83
lines changed

Sources/web3swift/Utils/EIP/EIP681.swift

Lines changed: 202 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,11 @@ extension Web3 {
2525
public struct EIP681Parameter {
2626
public var type: ABI.Element.ParameterType
2727
public var value: AnyObject
28-
<<<<<<< HEAD
29-
=======
3028

3129
public init(type: ABI.Element.ParameterType, value: AnyObject) {
3230
self.type = type
3331
self.value = value
3432
}
35-
>>>>>>> c159f067... Make value as any object in eip681 parameters
3633
}
3734
public var isPayRequest: Bool
3835
public var targetAddress: TargetAddress
@@ -108,7 +105,7 @@ extension Web3 {
108105
if tail == nil {
109106
return code
110107
}
111-
guard let components = URLComponents(string: tail!) else {return code}
108+
guard let components = URLComponents(string: tail!.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? tail!) else {return code}
112109
if components.path == "" {
113110
code.isPayRequest = true
114111
} else {
@@ -119,72 +116,16 @@ extension Web3 {
119116
var inputs = [ABI.Element.InOut]()
120117
for comp in queryItems {
121118
if let inputType = try? ABITypeParser.parseTypeString(comp.name) {
122-
guard let value = comp.value else {continue}
123-
var nativeValue: AnyObject? = nil
124-
switch inputType {
125-
case .address:
126-
let val = EIP681Code.TargetAddress(value)
127-
switch val {
128-
case .ethereumAddress(let ethereumAddress):
129-
nativeValue = ethereumAddress as AnyObject
130-
// default:
131-
// return nil
132-
case .ensAddress(let ens):
133-
do {
134-
let web = await web3(provider: InfuraProvider(Networks.fromInt(UInt(code.chainID ?? 1)) ?? Networks.Mainnet)!)
135-
let ensModel = ENS(web3: web)
136-
try await ensModel?.setENSResolver(withDomain: ens)
137-
let address = try await ensModel?.getAddress(forNode: ens)
138-
nativeValue = address as AnyObject
139-
} catch {
140-
return nil
141-
}
142-
}
143-
case .uint(bits: _):
144-
if let val = BigUInt(value, radix: 10) {
145-
nativeValue = val as AnyObject
146-
} else if let val = BigUInt(value.stripHexPrefix(), radix: 16) {
147-
nativeValue = val as AnyObject
148-
}
149-
case .int(bits: _):
150-
if let val = BigInt(value, radix: 10) {
151-
nativeValue = val as AnyObject
152-
} else if let val = BigInt(value.stripHexPrefix(), radix: 16) {
153-
nativeValue = val as AnyObject
154-
}
155-
case .string:
156-
nativeValue = value as AnyObject
157-
case .dynamicBytes:
158-
if let val = Data.fromHex(value) {
159-
nativeValue = val as AnyObject
160-
} else if let val = value.data(using: .utf8) {
161-
nativeValue = val as AnyObject
162-
}
163-
case .bytes(length: _):
164-
if let val = Data.fromHex(value) {
165-
nativeValue = val as AnyObject
166-
} else if let val = value.data(using: .utf8) {
167-
nativeValue = val as AnyObject
168-
}
169-
case .bool:
170-
switch value {
171-
case "true", "True", "TRUE", "1":
172-
nativeValue = true as AnyObject
173-
case "false", "False", "FALSE", "0":
174-
nativeValue = false as AnyObject
175-
default:
176-
nativeValue = true as AnyObject
177-
}
178-
default:
179-
continue
180-
}
181-
if nativeValue != nil {
182-
inputs.append(ABI.Element.InOut(name: String(inputNumber), type: inputType))
183-
code.parameters.append(EIP681Code.EIP681Parameter(type: inputType, value: nativeValue!))
184-
inputNumber = inputNumber + 1
185-
} else {
186-
return nil
187-
}
119+
guard let rawValue = comp.value,
120+
let functionArgument = parseFunctionArgument(inputType,
121+
rawValue.trimmingCharacters(in: .whitespacesAndNewlines),
122+
chainID: code.chainID ?? 0,
123+
inputNumber: inputNumber)
124+
else { continue }
125+
126+
inputs.append(functionArgument.argType)
127+
code.parameters.append(functionArgument.parameter)
128+
inputNumber = inputNumber + 1
188129
} else {
189130
switch comp.name {
190131
case "value":
@@ -235,5 +176,196 @@ extension Web3 {
235176
print(code)
236177
return code
237178
}
179+
180+
private static func parseFunctionArgument(_ inputType: ABI.Element.ParameterType,
181+
_ rawValue: String,
182+
chainID: BigUInt,
183+
inputNumber: Int) -> FunctionArgument? {
184+
var parsedValue: AnyObject? = nil
185+
switch inputType {
186+
case .address:
187+
let val = EIP681Code.TargetAddress(rawValue)
188+
switch val {
189+
case .ethereumAddress(let ethereumAddress):
190+
parsedValue = ethereumAddress as AnyObject
191+
case .ensAddress(let ens):
192+
do {
193+
let web = web3(provider: InfuraProvider(Networks.fromInt(Int(chainID)) ?? Networks.Mainnet)!)
194+
let ensModel = ENS(web3: web)
195+
try ensModel?.setENSResolver(withDomain: ens)
196+
let address = try ensModel?.getAddress(forNode: ens)
197+
parsedValue = address as AnyObject
198+
} catch {
199+
return nil
200+
}
201+
}
202+
case .uint(bits: _):
203+
if let val = BigUInt(rawValue, radix: 10) {
204+
parsedValue = val as AnyObject
205+
} else if let val = BigUInt(rawValue.stripHexPrefix(), radix: 16) {
206+
parsedValue = val as AnyObject
207+
}
208+
case .int(bits: _):
209+
if let val = BigInt(rawValue, radix: 10) {
210+
parsedValue = val as AnyObject
211+
} else if let val = BigInt(rawValue.stripHexPrefix(), radix: 16) {
212+
parsedValue = val as AnyObject
213+
}
214+
case .string:
215+
parsedValue = rawValue as AnyObject
216+
case .dynamicBytes:
217+
if let val = Data.fromHex(rawValue) {
218+
parsedValue = val as AnyObject
219+
} else if let val = rawValue.data(using: .utf8) {
220+
parsedValue = val as AnyObject
221+
}
222+
case .bytes(length: _):
223+
if let val = Data.fromHex(rawValue) {
224+
parsedValue = val as AnyObject
225+
} else if let val = rawValue.data(using: .utf8) {
226+
parsedValue = val as AnyObject
227+
}
228+
case .bool:
229+
switch rawValue {
230+
case "true", "True", "TRUE", "1":
231+
parsedValue = true as AnyObject
232+
case "false", "False", "FALSE", "0":
233+
parsedValue = false as AnyObject
234+
default:
235+
parsedValue = true as AnyObject
236+
}
237+
case let .array(type, length):
238+
var rawValues: [String] = []
239+
if case .array = type {
240+
guard let internalArrays = splitArrayOfArrays(rawValue),
241+
(length == 0 || UInt64(internalArrays.count) == length) else { return nil }
242+
rawValues = internalArrays
243+
} else if case let .tuple(internalTypes) = type {
244+
// TODO: implement!
245+
} else if case .string = type {
246+
guard let strings = splitArrayOfStrings(rawValue),
247+
(length == 0 || UInt64(strings.count) == length) else { return nil }
248+
rawValues = strings
249+
} else {
250+
let rawValue = String(rawValue.dropFirst().dropLast())
251+
rawValues = rawValue.split(separator: ",").map { String($0) }
252+
}
253+
254+
parsedValue = rawValues.compactMap {
255+
parseFunctionArgument(type,
256+
String($0),
257+
chainID: chainID,
258+
inputNumber: inputNumber)?
259+
.parameter
260+
.value
261+
} as AnyObject
262+
263+
guard (parsedValue as? [AnyObject])?.count == rawValues.count &&
264+
(length == 0 || UInt64(rawValues.count) == length) else { return nil }
265+
case let .tuple(types):
266+
// TODO: implement!
267+
return nil
268+
default: return nil
269+
}
270+
271+
guard let parsedValue = parsedValue else { return nil }
272+
return FunctionArgument(ABI.Element.InOut(name: String(inputNumber), type: inputType),
273+
EIP681Code.EIP681Parameter(type: inputType, value: parsedValue))
274+
}
275+
276+
// MARK: - Parsing functions for complex data structures
277+
278+
/// Attempts to split given ``rawValue`` into `[String]` where each element of that array
279+
/// represent a valid, stringified, array in of itself.
280+
/// With an input like `"[[],[],[]]"` the output is expected to be `["[]","[]","[]"]`.
281+
/// - Parameter rawValue: supposedly an array of arrays in a form `[[...],[...],...]`
282+
/// - Returns: separated stringified arrays, or `nil` if separation failed.
283+
private static func splitArrayOfArrays(_ rawValue: String) -> [String]? {
284+
/// Dropping first and last square brackets.
285+
/// That modifies the upper bound value of the first match of `squareBracketRegex`.
286+
let rawValue = String(rawValue.dropFirst().dropLast())
287+
288+
let squareBracketRegex = try! NSRegularExpression(pattern: "(\\[*)")
289+
let match = squareBracketRegex.firstMatch(in: rawValue, range: rawValue.fullNSRange)
290+
291+
guard let bracketsCount = match?.range.upperBound,
292+
bracketsCount > 0 else {
293+
return nil
294+
}
295+
296+
let splitRegex = try! NSRegularExpression(pattern: "(\\]){\(bracketsCount)},(\\[){\(bracketsCount)}")
297+
var indices: [Int] = splitRegex.matches(in: rawValue, range: rawValue.fullNSRange)
298+
.map { $0.range.lowerBound + bracketsCount }
299+
if !indices.isEmpty {
300+
indices.append(rawValue.count)
301+
var prevIndex = 0
302+
var result = [String]()
303+
for index in indices {
304+
result.append(rawValue[prevIndex..<index])
305+
prevIndex = index + 1
306+
}
307+
return result
308+
}
309+
return [rawValue]
310+
}
311+
312+
/// Attempts to split a string that represents an array of strings.
313+
/// Example:
314+
///
315+
/// // input
316+
/// "[\"1\",\"abcd,efgh\",\"-=-=-\"]"
317+
/// // output
318+
/// ["1","abcd,efgh","-=-=-"]
319+
///
320+
/// // input
321+
/// "[1,abcd,efgh,-=-=-]"
322+
/// // output
323+
/// ["1","abcd","efgh","-=-=-"]
324+
///
325+
/// - Parameter rawValue: stringified array of strings.
326+
/// - Returns: an array of separated individual elements, or `nil` if separation failed.
327+
private static func splitArrayOfStrings(_ rawValue: String) -> [String]? {
328+
/// Dropping first and last square brackets to exclude them from the first and the last separated element.
329+
let rawValue = String(rawValue.dropFirst().dropLast())
330+
331+
let elementsBoundary = try! NSRegularExpression(pattern: "\",\"")
332+
var indices = Array(elementsBoundary
333+
.matches(in: rawValue, range: rawValue.fullNSRange)
334+
.map { result in
335+
result.range.lowerBound + 1
336+
})
337+
338+
if !indices.isEmpty {
339+
indices.append(rawValue.count)
340+
var prevIndex = 0
341+
var rawValues = [String]()
342+
for index in indices {
343+
var argument = rawValue[prevIndex..<index]
344+
if let index = argument.firstIndex(of: "\""),
345+
argument.distance(from: argument.startIndex, to: index) == 0 {
346+
argument = String(argument.dropFirst())
347+
}
348+
if let index = argument.lastIndex(of: "\""),
349+
argument.distance(from: argument.endIndex, to: index) == -1 {
350+
argument = String(argument.dropLast())
351+
}
352+
rawValues.append(argument)
353+
prevIndex = index + 1
354+
}
355+
return rawValues
356+
}
357+
return rawValue.split(separator: ",").map { String($0) }
358+
}
359+
}
360+
}
361+
362+
fileprivate class FunctionArgument {
363+
let argType: ABI.Element.InOut
364+
let parameter: Web3.EIP681Code.EIP681Parameter
365+
366+
init(_ argType: ABI.Element.InOut,
367+
_ parameter: Web3.EIP681Code.EIP681Parameter) {
368+
self.argType = argType
369+
self.parameter = parameter
238370
}
239371
}

0 commit comments

Comments
 (0)