@@ -77,7 +77,7 @@ public extension ABI {
7777 public let type : ParameterType
7878
7979 public init ( name: String , type: ParameterType ) {
80- self . name = name
80+ self . name = name. trim ( )
8181 self . type = type
8282 }
8383 }
@@ -91,7 +91,7 @@ public extension ABI {
9191 public let payable : Bool
9292
9393 public init ( name: String ? , inputs: [ InOut ] , outputs: [ InOut ] , constant: Bool , payable: Bool ) {
94- self . name = name
94+ self . name = name? . trim ( )
9595 self . inputs = inputs
9696 self . outputs = outputs
9797 self . constant = constant
@@ -103,6 +103,7 @@ public extension ABI {
103103 public let inputs : [ InOut ]
104104 public let constant : Bool
105105 public let payable : Bool
106+
106107 public init ( inputs: [ InOut ] , constant: Bool , payable: Bool ) {
107108 self . inputs = inputs
108109 self . constant = constant
@@ -126,7 +127,7 @@ public extension ABI {
126127 public let anonymous : Bool
127128
128129 public init ( name: String , inputs: [ Input ] , anonymous: Bool ) {
129- self . name = name
130+ self . name = name. trim ( )
130131 self . inputs = inputs
131132 self . anonymous = anonymous
132133 }
@@ -137,7 +138,7 @@ public extension ABI {
137138 public let indexed : Bool
138139
139140 public init ( name: String , type: ParameterType , indexed: Bool ) {
140- self . name = name
141+ self . name = name. trim ( )
141142 self . type = type
142143 self . indexed = indexed
143144 }
@@ -155,16 +156,16 @@ public extension ABI {
155156 /// Custom structured error type available since solidity 0.8.4
156157 public struct EthError {
157158 public let name : String
158- public let inputs : [ Input ]
159+ public let inputs : [ InOut ]
159160
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+ }
163165
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
168169 }
169170 }
170171 }
@@ -202,7 +203,7 @@ extension ABI.Element.Function {
202203
203204 /// Encode parameters of a given contract method
204205 /// - Parameter parameters: Parameters to pass to Ethereum contract
205- /// - Returns: Encoded data
206+ /// - Returns: Encoded data
206207 public func encodeParameters( _ parameters: [ AnyObject ] ) -> Data ? {
207208 guard parameters. count == inputs. count,
208209 let data = ABIEncoder . encode ( types: inputs, values: parameters) else { return nil }
@@ -264,73 +265,168 @@ extension ABI.Element.Function {
264265 return Core . decodeInputData ( rawData, methodEncoding: methodEncoding, inputs: inputs)
265266 }
266267
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+ }
284321
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+ }
288326
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+ }
297330
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]
299340 }
300341 }
342+ return returnArray
343+ }
301344
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? " ]
326389 }
327390
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]
330409 }
331410
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
334430 }
335431}
336432
0 commit comments