Skip to content

Commit a6c0076

Browse files
chore: ABIEncoder - docs, tests, cleanup
1 parent e90c225 commit a6c0076

File tree

2 files changed

+184
-54
lines changed

2 files changed

+184
-54
lines changed

Sources/Core/EthereumABI/ABIEncoding.swift

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import Foundation
77
import BigInt
88

9-
public struct ABIEncoder { }
10-
11-
extension ABIEncoder {
9+
public struct ABIEncoder {
10+
/// Attempts to convert given value to `BigUInt`.
11+
/// Supported types are `BigUInt`, `BigInt`, `String` as hex and decimal, `UInt[8-64]`, `Int[8-64]` and `Data`.
12+
/// All negative values will return `nil`.
13+
/// - Parameter value: an arbitrary object.
14+
/// - Returns: converted value or `nil` if types is not support or initialization failed.
1215
public static func convertToBigUInt(_ value: AnyObject) -> BigUInt? {
1316
switch value {
1417
case let v as BigUInt:
@@ -21,15 +24,11 @@ extension ABIEncoder {
2124
return v.magnitude
2225
}
2326
case let v as String:
24-
let base10 = BigUInt(v, radix: 10)
25-
if base10 != nil {
26-
return base10!
27-
}
28-
let base16 = BigUInt(v.stripHexPrefix(), radix: 16)
29-
if base16 != nil {
30-
return base16!
27+
let v = v.trimmingCharacters(in: .whitespacesAndNewlines)
28+
if v.starts(with: "-") {
29+
return nil
3130
}
32-
break
31+
return BigUInt(v, radix: 10) ?? BigUInt(v.stripHexPrefix(), radix: 16)
3332
case let v as UInt:
3433
return BigUInt(v)
3534
case let v as UInt8:
@@ -41,37 +40,35 @@ extension ABIEncoder {
4140
case let v as UInt64:
4241
return BigUInt(v)
4342
case let v as Int:
44-
return BigUInt(v)
43+
return v < 0 ? nil : BigUInt(v)
4544
case let v as Int8:
46-
return BigUInt(v)
45+
return v < 0 ? nil : BigUInt(v)
4746
case let v as Int16:
48-
return BigUInt(v)
47+
return v < 0 ? nil : BigUInt(v)
4948
case let v as Int32:
50-
return BigUInt(v)
49+
return v < 0 ? nil : BigUInt(v)
5150
case let v as Int64:
51+
return v < 0 ? nil : BigUInt(v)
52+
case let v as Data:
5253
return BigUInt(v)
5354
default:
5455
return nil
5556
}
56-
return nil
5757
}
5858

59+
/// Attempts to convert given value to `BigInt`.
60+
/// Supported types are `BigUInt`, `BigInt`, `String` as hex and decimal, `UInt[8-64]`, `Int[8-64]` and `Data`.
61+
/// - Parameter value: an arbitrary object.
62+
/// - Returns: converted value or `nil` if types is not support or initialization failed.
5963
public static func convertToBigInt(_ value: AnyObject) -> BigInt? {
6064
switch value {
6165
case let v as BigUInt:
6266
return BigInt(v)
6367
case let v as BigInt:
6468
return v
6569
case let v as String:
66-
let base10 = BigInt(v, radix: 10)
67-
if base10 != nil {
68-
return base10
69-
}
70-
let base16 = BigInt(v.stripHexPrefix(), radix: 16)
71-
if base16 != nil {
72-
return base16
73-
}
74-
break
70+
let v = v.trimmingCharacters(in: .whitespacesAndNewlines)
71+
return BigInt(v, radix: 10) ?? BigInt(v.stripHexPrefix(), radix: 16)
7572
case let v as UInt:
7673
return BigInt(v)
7774
case let v as UInt8:
@@ -92,27 +89,29 @@ extension ABIEncoder {
9289
return BigInt(v)
9390
case let v as Int64:
9491
return BigInt(v)
92+
case let v as Data:
93+
return BigInt(v)
9594
default:
9695
return nil
9796
}
98-
return nil
9997
}
10098

99+
/// Attempts to convert given object into `Data`.
100+
/// Used as a part of ABI encoding process.
101+
/// Supported types are `Data`, `String`, `[UInt8]`, ``EthereumAddress`` and `[IntegerLiteralType]`.
102+
/// Note: if `String` has `0x` prefix an attempt to interpret it as a hexadecimal number will take place. Otherwise, UTF-8 bytes are returned.
103+
/// - Parameter value: any object.
104+
/// - Returns: `Data` representation of an object ready for ABI encoding.
101105
public static func convertToData(_ value: AnyObject) -> Data? {
102106
switch value {
103107
case let d as Data:
104108
return d
105109
case let d as String:
106-
if d.hasHexPrefix() {
107-
let hex = Data.fromHex(d)
108-
if hex != nil {
109-
return hex
110-
}
111-
}
112-
let str = d.data(using: .utf8)
113-
if str != nil {
114-
return str
110+
if d.hasHexPrefix(),
111+
let hex = Data.fromHex(d) {
112+
return hex
115113
}
114+
return d.data(using: .utf8)
116115
case let d as [UInt8]:
117116
return Data(d)
118117
case let d as EthereumAddress:
@@ -127,14 +126,17 @@ extension ABIEncoder {
127126
default:
128127
return nil
129128
}
130-
return nil
131129
}
132130

133-
/// Encode Elements In Out
131+
/// Performs ABI encoding conforming to [the documentation of encoding](https://docs.soliditylang.org/en/develop/abi-spec.html#basic-design) in Solidity.
132+
/// Overloading to support `ABI.Element.InOut` as the type of the `types` array.
133+
/// Identical to use of `web3.eth.abi.encodeParameters` in web3.js.
134134
/// - Parameters:
135-
/// - types: Contract element InOut to encode
136-
/// - values: Contract values of a given element to encode
137-
/// - Returns: Encoded data
135+
/// - types: an array of values' ABI types. Must be declared in the same order as entries in `values` or encoding will fail;
136+
/// - values: an array of values to encode. Must be declared in the same order as entries in `types` or encoding will fail;
137+
/// - Returns: ABI encoded data, e.g. function call parameters. Returns `nil` if:
138+
/// - `types.count != values.count`;
139+
/// - encoding of at least one value has failed (e.g. type mismatch).
138140
public static func encode(types: [ABI.Element.InOut], values: [AnyObject]) -> Data? {
139141
guard types.count == values.count else {return nil}
140142
let params = types.compactMap { el -> ABI.Element.ParameterType in
@@ -143,11 +145,14 @@ extension ABIEncoder {
143145
return encode(types: params, values: values)
144146
}
145147

146-
/// Encode Elements Prarmeter Type
148+
/// Performs ABI encoding conforming to [the documentation of encoding](https://docs.soliditylang.org/en/develop/abi-spec.html#basic-design) in Solidity.
149+
/// Identical to use of `web3.eth.abi.encodeParameters` in web3.js.
147150
/// - Parameters:
148-
/// - types: Contract parameters type to encode
149-
/// - values: Contract values of a given element to encode
150-
/// - Returns: Encoded data
151+
/// - types: an array of values' ABI types. Must be declared in the same order as entries in `values` or encoding will fail;
152+
/// - values: an array of values to encode. Must be declared in the same order as entries in `types` or encoding will fail;
153+
/// - Returns: ABI encoded data, e.g. function call parameters. Returns `nil` if:
154+
/// - `types.count != values.count`;
155+
/// - encoding of at least one value has failed (e.g. type mismatch).
151156
public static func encode(types: [ABI.Element.ParameterType], values: [AnyObject]) -> Data? {
152157
guard types.count == values.count else {return nil}
153158
var tails = [Data]()
@@ -186,6 +191,29 @@ extension ABIEncoder {
186191
return headsConcatenated + tailsConcatenated
187192
}
188193

194+
/// Performs ABI encoding conforming to [the documentation of encoding](https://docs.soliditylang.org/en/develop/abi-spec.html#basic-design) in Solidity
195+
/// but **it does not add the data offset for dynamic types!!** To return single value **with data offset** use the following instead:
196+
/// ```
197+
/// ABIEncoder.encode(types: [type], values: [value] as [AnyObject])
198+
/// ```
199+
/// Almost identical to use of `web3.eth.abi.encodeParameter` in web3.js.
200+
/// Calling `web3.eth.abi.encodeParameter('string','test')` in web3.js will return the following:
201+
/// ```
202+
/// 0x0000000000000000000000000000000000000000000000000000000000000020
203+
/// 0000000000000000000000000000000000000000000000000000000000000004
204+
/// 7465737400000000000000000000000000000000000000000000000000000000
205+
/// ```
206+
/// but calling `ABIEncoder.encodeSingleType(type: .string, value: "test" as AnyObject)` will return:
207+
/// ```
208+
/// 0x0000000000000000000000000000000000000000000000000000000000000004
209+
/// 7465737400000000000000000000000000000000000000000000000000000000
210+
/// ```
211+
/// - Parameters:
212+
/// - type: ABI type of the `value`;
213+
/// - value: value to encode.
214+
/// - Returns: ABI encoded data, e.g. function call parameters. Returns `nil` if:
215+
/// - `types.count != values.count`;
216+
/// - encoding has failed (e.g. type mismatch).
189217
public static func encodeSingleType(type: ABI.Element.ParameterType, value: AnyObject) -> Data? {
190218
switch type {
191219
case .uint:
@@ -395,14 +423,21 @@ extension ABIEncoder {
395423
// MARK: - SoliditySHA3 implementation based on web3js
396424

397425
public extension ABIEncoder {
398-
/**
399-
A convenience implementation of web3js [soliditySha3](https://web3js.readthedocs.io/en/v1.2.11/web3-utils.html?highlight=soliditySha3#soliditysha3)
400-
that is based on web3swift [`ABIEncoder`](https://github.com/skywinder/web3swift/blob/develop/Sources/web3swift/EthereumABI/ABIEncoding.swift ).
401-
*/
426+
427+
/// A convenience implementation of web3js [soliditySha3](https://web3js.readthedocs.io/en/v1.2.11/web3-utils.html?highlight=soliditySha3#soliditysha3)
428+
/// that is based on web3swift [`ABIEncoder`](https://github.com/skywinder/web3swift/blob/develop/Sources/web3swift/EthereumABI/ABIEncoding.swift ).
429+
/// - Parameter values: an array of values to hash. Supported types are: `Int/UInt` (any of 8, 16, 32, 64 bits long),
430+
/// decimal or hexadecimal `String`, `Bool`, `Data`, `[UInt8]`, `EthereumAddress`, `[IntegerLiteralType]`, `BigInt` or `BigUInt`.
431+
/// - Returns: solidity SHA3, `nil` if hashing failed or throws if type is not supported.
402432
static func soliditySha3(_ values: [Any]) throws -> Data {
403433
try abiEncode(values).sha3(.keccak256)
404434
}
405435

436+
/// A convenience implementation of web3js [soliditySha3](https://web3js.readthedocs.io/en/v1.2.11/web3-utils.html?highlight=soliditySha3#soliditysha3)
437+
/// that is based on web3swift [`ABIEncoder`](https://github.com/skywinder/web3swift/blob/develop/Sources/web3swift/EthereumABI/ABIEncoding.swift ).
438+
/// - Parameter value: a value to hash. Supported types are: `Int/UInt` (any of 8, 16, 32, 64 bits long),
439+
/// decimal or hexadecimal `String`, `Bool`, `Data`, `[UInt8]`, `EthereumAddress`, `[IntegerLiteralType]`, `BigInt` or `BigUInt`.
440+
/// - Returns: solidity SHA3, `nil` if hashing failed or throws if type is not supported.
406441
static func soliditySha3(_ value: Any) throws -> Data {
407442
if let values = value as? [Any] {
408443
return try abiEncode(values).sha3(.keccak256)
@@ -460,6 +495,6 @@ public extension ABIEncoder {
460495
} else if let data = ABIEncoder.convertToData(value as AnyObject) {
461496
return data
462497
}
463-
throw Web3Error.inputError(desc: "SoliditySha3: `abiEncode` accepts an Int/UInt (any of 8, 16, 32, 64 bits long), HEX string, Bool, Data, BigInt or BigUInt instance. Given value is of type \(type(of: value)).")
498+
throw Web3Error.inputError(desc: "SoliditySha3: `abiEncode` accepts an Int/UInt (any of 8, 16, 32, 64 bits long), decimal or HEX string, Bool, Data, [UInt8], EthereumAddress, [IntegerLiteralType], BigInt or BigUInt instance. Given value is of type \(type(of: value)).")
464499
}
465500
}

0 commit comments

Comments
 (0)