@@ -77,7 +77,7 @@ public extension ABI {
77
77
public let type : ParameterType
78
78
79
79
public init ( name: String , type: ParameterType ) {
80
- self . name = name
80
+ self . name = name. trim ( )
81
81
self . type = type
82
82
}
83
83
}
@@ -91,7 +91,7 @@ public extension ABI {
91
91
public let payable : Bool
92
92
93
93
public init ( name: String ? , inputs: [ InOut ] , outputs: [ InOut ] , constant: Bool , payable: Bool ) {
94
- self . name = name
94
+ self . name = name? . trim ( )
95
95
self . inputs = inputs
96
96
self . outputs = outputs
97
97
self . constant = constant
@@ -103,6 +103,7 @@ public extension ABI {
103
103
public let inputs : [ InOut ]
104
104
public let constant : Bool
105
105
public let payable : Bool
106
+
106
107
public init ( inputs: [ InOut ] , constant: Bool , payable: Bool ) {
107
108
self . inputs = inputs
108
109
self . constant = constant
@@ -126,7 +127,7 @@ public extension ABI {
126
127
public let anonymous : Bool
127
128
128
129
public init ( name: String , inputs: [ Input ] , anonymous: Bool ) {
129
- self . name = name
130
+ self . name = name. trim ( )
130
131
self . inputs = inputs
131
132
self . anonymous = anonymous
132
133
}
@@ -137,7 +138,7 @@ public extension ABI {
137
138
public let indexed : Bool
138
139
139
140
public init ( name: String , type: ParameterType , indexed: Bool ) {
140
- self . name = name
141
+ self . name = name. trim ( )
141
142
self . type = type
142
143
self . indexed = indexed
143
144
}
@@ -155,16 +156,16 @@ public extension ABI {
155
156
/// Custom structured error type available since solidity 0.8.4
156
157
public struct EthError {
157
158
public let name : String
158
- public let inputs : [ Input ]
159
+ public let inputs : [ InOut ]
159
160
160
- public struct Input {
161
- public let name : String
162
- public let type : ParameterType
161
+ /// e.g. `CustomError(uint32, address sender)`
162
+ public var errorDeclaration : String {
163
+ " \( name) ( \( inputs. map { " \( $0. type. abiRepresentation) \( $0. name) " . trim ( ) } . joined ( separator: " , " ) ) ) "
164
+ }
163
165
164
- public init ( name: String , type: ParameterType ) {
165
- self . name = name
166
- self . type = type
167
- }
166
+ public init ( name: String , inputs: [ InOut ] = [ ] ) {
167
+ self . name = name. trim ( )
168
+ self . inputs = inputs
168
169
}
169
170
}
170
171
}
@@ -202,7 +203,7 @@ extension ABI.Element.Function {
202
203
203
204
/// Encode parameters of a given contract method
204
205
/// - Parameter parameters: Parameters to pass to Ethereum contract
205
- /// - Returns: Encoded data
206
+ /// - Returns: Encoded data
206
207
public func encodeParameters( _ parameters: [ AnyObject ] ) -> Data ? {
207
208
guard parameters. count == inputs. count,
208
209
let data = ABIEncoder . encode ( types: inputs, values: parameters) else { return nil }
@@ -264,73 +265,168 @@ extension ABI.Element.Function {
264
265
return Core . decodeInputData ( rawData, methodEncoding: methodEncoding, inputs: inputs)
265
266
}
266
267
267
- public func decodeReturnData( _ data: Data ) -> [ String : Any ] ? {
268
- // the response size greater than equal 100 bytes, when read function aborted by "require" statement.
269
- // if "require" statement has no message argument, the response is empty (0 byte).
270
- if data. bytes. count >= 100 {
271
- let check00_31 = BigUInt ( " 08C379A000000000000000000000000000000000000000000000000000000000 " , radix: 16 ) !
272
- let check32_63 = BigUInt ( " 0000002000000000000000000000000000000000000000000000000000000000 " , radix: 16 ) !
273
-
274
- // check data[00-31] and data[32-63]
275
- if check00_31 == BigUInt ( data [ 0 ... 31 ] ) && check32_63 == BigUInt ( data [ 32 ... 63 ] ) {
276
- // data.bytes[64-67] contains the length of require message
277
- let len = ( Int ( data. bytes [ 64 ] ) <<24 ) | ( Int ( data. bytes [ 65 ] ) <<16 ) | ( Int ( data. bytes [ 66 ] ) <<8 ) | Int ( data. bytes [ 67 ] )
278
-
279
- let message = String ( bytes: data. bytes [ 68 ..< ( 68 + len) ] , encoding: . utf8) !
280
-
281
- print ( " read function aborted by require statement: \( message) " )
282
-
283
- var returnArray = [ String: Any] ( )
268
+ /// Decodes data returned by a function call. Able to decode `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls.
269
+ /// - Parameters:
270
+ /// - data: bytes returned by a function call;
271
+ /// - errors: optional dictionary of known errors that could be returned by the function you called. Used to decode the error information.
272
+ /// - Returns: a dictionary containing decoded data mappend to indices and names of returned values if these are not `nil`.
273
+ /// If `data` is an error response returns dictionary containing all available information about that specific error. Read more for details.
274
+ ///
275
+ /// Return cases:
276
+ /// - when no `outputs` declared and `data` is not an error response:
277
+ ///```swift
278
+ ///["_success": true]
279
+ ///```
280
+ /// - when `outputs` declared and decoding completed successfully:
281
+ ///```swift
282
+ ///["_success": true, "0": value_1, "1": value_2, ...]
283
+ ///```
284
+ ///Additionally this dictionary will have mappings to output names if these names are specified in the ABI;
285
+ /// - function call was aborted using `revert(message)` or `require(expression, message)`:
286
+ ///```swift
287
+ ///["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]`
288
+ ///```
289
+ /// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type:
290
+ ///```swift
291
+ ///["_success": false,
292
+ ///"_abortedByRevertOrRequire": true,
293
+ ///"_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)`
294
+ ///"0": error_arg1,
295
+ ///"1": error_arg2,
296
+ ///...,
297
+ ///"error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress`
298
+ ///"error_arg2_name": error_arg2, // Otherwise, you can query them by position index.
299
+ ///...]
300
+ ///```
301
+ ///- in case of any error:
302
+ ///```swift
303
+ ///["_success": false, "_failureReason": String]
304
+ ///```
305
+ ///Error reasons include:
306
+ /// - `outputs` declared but at least one value failed to be decoded;
307
+ /// - `data.count` is less than `outputs.count * 32`;
308
+ /// - `outputs` defined and `data` is empty;
309
+ /// - `data` represent reverted transaction
310
+ ///
311
+ /// How `revert(string)` and `require(expression, string)` return value is decomposed:
312
+ /// - `08C379A0` function selector for `Error(string)`;
313
+ /// - next 32 bytes are the data offset;
314
+ /// - next 32 bytes are the error message length;
315
+ /// - the next N bytes, where N >= 32, are the message bytes
316
+ /// - the rest are 0 bytes padding.
317
+ public func decodeReturnData( _ data: Data , errors: [ String : ABI . Element . EthError ] ? = nil ) -> [ String : Any ] {
318
+ if let decodedError = decodeErrorResponse ( data, errors: errors) {
319
+ return decodedError
320
+ }
284
321
285
- // set infomation
286
- returnArray [ " _abortedByRequire " ] = true
287
- returnArray [ " _errorMessageFromRequire " ] = message
322
+ guard !outputs. isEmpty else {
323
+ NSLog ( " Function doesn't have any output types to decode given data. " )
324
+ return [ " _success " : true ]
325
+ }
288
326
289
- // set empty values
290
- for i in 0 ..< outputs. count {
291
- let name = " \( i) "
292
- returnArray [ name] = outputs [ i] . type. emptyValue
293
- if outputs [ i] . name != " " {
294
- returnArray [ outputs [ i] . name] = outputs [ i] . type. emptyValue
295
- }
296
- }
327
+ guard outputs. count * 32 <= data. count else {
328
+ return [ " _success " : false , " _failureReason " : " Bytes count must be at least \( outputs. count * 32 ) . Given \( data. count) . Decoding will fail. " ]
329
+ }
297
330
298
- return returnArray
331
+ // TODO: need improvement - we should be able to tell which value failed to be decoded
332
+ guard let values = ABIDecoder . decode ( types: outputs, data: data) else {
333
+ return [ " _success " : false , " _failureReason " : " Failed to decode at least one value. " ]
334
+ }
335
+ var returnArray : [ String : Any ] = [ " _success " : true ]
336
+ for i in outputs. indices {
337
+ returnArray [ " \( i) " ] = values [ i]
338
+ if !outputs[ i] . name. isEmpty {
339
+ returnArray [ outputs [ i] . name] = values [ i]
299
340
}
300
341
}
342
+ return returnArray
343
+ }
301
344
302
- var returnArray = [ String: Any] ( )
303
-
304
- // the "require" statement with no message argument will be caught here
305
- if data. count == 0 && outputs. count == 1 {
306
- let name = " 0 "
307
- let value = outputs [ 0 ] . type. emptyValue
308
- returnArray [ name] = value
309
- if outputs [ 0 ] . name != " " {
310
- returnArray [ outputs [ 0 ] . name] = value
311
- }
312
- } else {
313
- guard outputs. count * 32 <= data. count else { return nil }
314
-
315
- var i = 0
316
- guard let values = ABIDecoder . decode ( types: outputs, data: data) else { return nil }
317
- for output in outputs {
318
- let name = " \( i) "
319
- returnArray [ name] = values [ i]
320
- if output. name != " " {
321
- returnArray [ output. name] = values [ i]
322
- }
323
- i = i + 1
324
- }
325
- // set a flag to detect the request succeeded
345
+ /// Decodes `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls.
346
+ /// If `data` is empty and `outputs` are not empty it's considered that data is a result of `revert()` or `require(false)`.
347
+ /// - Parameters:
348
+ /// - data: returned function call data to decode;
349
+ /// - errors: optional known errors that could be thrown by the function you called.
350
+ /// - Returns: dictionary containing information about the error thrown by the function call.
351
+ ///
352
+ /// What could be returned:
353
+ /// - `nil` if data doesn't represent an error or it failed to be mapped to any of the `errors` or `Error(string)` types;
354
+ /// - `nil` is `data.isEmpty` and `outputs.isEmpty`;
355
+ /// - `data.isEmpty` and `!outputs.isEmpty`:
356
+ /// ```swift
357
+ /// ["_success": false,
358
+ /// "_failureReason": "Cannot decode empty data. X outputs are expected: [outputs_types]. Was this a result of en empty `require(false)` or `revert()` call?"]
359
+ /// ```
360
+ /// - function call was aborted using `revert(message)` or `require(expression, message)`:
361
+ /// ```swift
362
+ /// ["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]`
363
+ /// ```
364
+ /// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type:
365
+ /// ```swift
366
+ /// ["_success": false,
367
+ /// "_abortedByRevertOrRequire": true,
368
+ /// "_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)`
369
+ /// "0": error_arg1,
370
+ /// "1": error_arg2,
371
+ /// ...,
372
+ /// "error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress`
373
+ /// "error_arg2_name": error_arg2, // Otherwise, you can query them by position index.
374
+ /// ...]
375
+ ///
376
+ /// /// or if custo error found but decoding failed
377
+ /// ["_success": false,
378
+ /// "_abortedByRevertOrRequire": true,
379
+ /// // "_error" can contain value like `MyCustomError(uint256, address senderAddress)`
380
+ /// "_error": error_name_and_types,
381
+ /// // "_parsingError" is optional and is present only if decoding of custom error arguments failed
382
+ /// "_parsingError": "Data matches MyCustomError(uint256, address senderAddress) but failed to be decoded."]
383
+ /// ```
384
+ public func decodeErrorResponse( _ data: Data , errors: [ String : ABI . Element . EthError ] ? = nil ) -> [ String : Any ] ? {
385
+ /// If data is empty and outputs are expected it is treated as a `require(expression)` or `revert()` call with no message.
386
+ /// In solidity `require(false)` and `revert()` calls return empty error response.
387
+ if data. isEmpty && !outputs. isEmpty {
388
+ return [ " _success " : false , " _failureReason " : " Cannot decode empty data. \( outputs. count) outputs are expected: \( outputs. map { $0. type. abiRepresentation } ) . Was this a result of en empty `require(false)` or `revert()` call? " ]
326
389
}
327
390
328
- if returnArray. isEmpty {
329
- return nil
391
+ /// Explanation of this condition:
392
+ /// When `revert(string)` or `require(false, string)` are called in soliditiy they produce
393
+ /// an error, specifically an instance of default `Error(string)` type.
394
+ /// 1) The total number of bytes returned are at least 100.
395
+ /// 2) The function selector for `Error(string)` is `08C379A0`;
396
+ /// 3) Data offset must be present. Hexadecimal value of `0000...0020` is 32 in decimal. Reasoning for `BigInt(...) == 32`.
397
+ /// 4) `messageLength` is used to determine where message bytes end to decode string correctly.
398
+ /// 5) The rest of the `data` must be 0 bytes or empty.
399
+ if data. bytes. count >= 100 ,
400
+ Data ( data [ 0 ..< 4 ] ) == Data . fromHex ( " 08C379A0 " ) ,
401
+ BigInt ( data [ 4 ..< 36 ] ) == 32 ,
402
+ let messageLength = Int ( Data ( data [ 36 ..< 68 ] ) . toHexString ( ) , radix: 16 ) ,
403
+ let message = String ( bytes: data. bytes [ 68 ..< ( 68 + messageLength) ] , encoding: . utf8) ,
404
+ ( 68 + messageLength == data. count || data. bytes [ 68 + messageLength..< data. count] . reduce ( 0 ) { $0 + $1 } == 0 ) {
405
+ return [ " _success " : false ,
406
+ " _failureReason " : " `revert(string)` or `require(expression, string)` was executed. " ,
407
+ " _abortedByRevertOrRequire " : true ,
408
+ " _errorMessage " : message]
330
409
}
331
410
332
- returnArray [ " _success " ] = true
333
- return returnArray
411
+ if data. count >= 4 ,
412
+ let errors = errors,
413
+ let customError = errors [ data [ 0 ..< 4 ] . toHexString ( ) . stripHexPrefix ( ) ] {
414
+ var errorResponse : [ String : Any ] = [ " _success " : false , " _abortedByRevertOrRequire " : true , " _error " : customError. errorDeclaration]
415
+
416
+ if ( data. count > 32 && !customError. inputs. isEmpty) ,
417
+ let decodedInputs = ABIDecoder . decode ( types: customError. inputs, data: Data ( data [ 4 ..< data. count] ) ) {
418
+ for idx in decodedInputs. indices {
419
+ errorResponse [ " \( idx) " ] = decodedInputs [ idx]
420
+ if !customError. inputs [ idx] . name. isEmpty {
421
+ errorResponse [ customError. inputs [ idx] . name] = decodedInputs [ idx]
422
+ }
423
+ }
424
+ } else if !customError. inputs. isEmpty {
425
+ errorResponse [ " _parsingError " ] = " Data matches \( customError. errorDeclaration) but failed to be decoded. "
426
+ }
427
+ return errorResponse
428
+ }
429
+ return nil
334
430
}
335
431
}
336
432
0 commit comments