Skip to content

Commit

Permalink
feat: performance optimizations for string manipulation (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
koraykoska authored Oct 30, 2022
1 parent 61d72b4 commit 561c1ff
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 31 deletions.
10 changes: 5 additions & 5 deletions Sources/ContractABI/ABI/ABIDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ public struct ABIDecoder {
}
}

var currentIndex = 0
var currentIndex = hexString.startIndex
var tailsToBeParsed: [(dataLocation: Int, param: SolidityParameter)] = []
for i in 0..<outputs.count {
let output = outputs[i]

if output.type.isDynamic {
// Head
let headStartIndex = hexString.index(hexString.startIndex, offsetBy: currentIndex)
let headStartIndex = currentIndex
let headEndIndex = hexString.index(headStartIndex, offsetBy: 64)
let subHex = String(hexString[headStartIndex..<headEndIndex])

Expand All @@ -103,22 +103,22 @@ public struct ABIDecoder {
let dataLocation = Int(UInt(indexBigUInt))

// Bump index (faster than removing)
currentIndex += 64
currentIndex = headEndIndex

// Tails need to be parsed once we are done with the static parts (current block)
tailsToBeParsed.append((dataLocation: dataLocation, param: output))
} else {
// Length as hex
let length = Int(output.type.staticPartLength) * 2

let startIndex = hexString.index(hexString.startIndex, offsetBy: currentIndex)
let startIndex = currentIndex
let endIndex = hexString.index(startIndex, offsetBy: length)
let subHex = String(hexString[startIndex..<endIndex])

returnDictionary[output.name] = try decodeType(type: output.type, hexString: subHex, components: output.components)

// Bump index (faster than removing)
currentIndex += length
currentIndex = endIndex
}
}

Expand Down
38 changes: 24 additions & 14 deletions Sources/Core/Toolbox/Bytes+HexString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,45 @@

import Foundation

fileprivate let hexMapping = Array("0123456789abcdef")

extension Array where Element == Byte {

func hexString(prefix: Bool) -> String {
var str = prefix ? "0x" : ""
var charArray: [String.Element] = [String.Element](repeating: "0", count: self.count * 2)

for b in self {
str += String(format: "%02x", b)
for i in 0..<self.count {
charArray[i * 2] = hexMapping[Int(self[i]) / 16]
charArray[(i * 2) + 1] = hexMapping[Int(self[i]) % 16]
}

return str
if prefix {
return "0x\(String(charArray))"
} else {
return String(charArray)
}
}

func quantityHexString(prefix: Bool) -> String {
var str = prefix ? "0x" : ""

// Remove leading zero bytes
let bytes = self.trimLeadingZeros()

if bytes.count > 0 {
// If there is one leading zero (4 bit) left, this one removes it
str += String(bytes[0], radix: 16)
var hex = ""
if bytes.count == 0 {
hex = "0"
} else {
hex = bytes.hexString(prefix: false)

for i in 1..<bytes.count {
str += String(format: "%02x", bytes[i])
// If there is one leading zero (4 bit) left, this one removes it
if hex.starts(with: "0") {
hex = String(hex[hex.index(hex.startIndex, offsetBy: 1)...])
}
} else {
str += "0"
}

return str
if prefix {
return "0x\(hex)"
} else {
return hex
}
}
}
46 changes: 37 additions & 9 deletions Sources/Core/Toolbox/String+HexBytes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,38 @@

import Foundation

fileprivate let hexMapping: [String.Element: UInt8] = [
"0": 0b0000,
"1": 0b0001,
"2": 0b0010,
"3": 0b0011,
"4": 0b0100,
"5": 0b0101,
"6": 0b0110,
"7": 0b0111,
"8": 0b1000,
"9": 0b1001,

"a": 0b1010,
"b": 0b1011,
"c": 0b1100,
"d": 0b1101,
"e": 0b1110,
"f": 0b1111,

"A": 0b1010,
"B": 0b1011,
"C": 0b1100,
"D": 0b1101,
"E": 0b1110,
"F": 0b1111
]

extension String {

/// Convert a hex string "0xFF" or "FF" to Bytes
func hexBytes() throws -> Bytes {
var string = self
// Check if we have a complete byte
guard !string.isEmpty else {
return Bytes()
}

if string.count >= 2 {
let pre = string.startIndex
Expand All @@ -26,6 +49,11 @@ extension String {
}
}

// Check if we have a complete byte
guard !string.isEmpty else {
return Bytes()
}

//normalize string, since hex strings can omit leading 0
string = string.count % 2 == 0 ? string : "0" + string

Expand Down Expand Up @@ -70,14 +98,14 @@ extension String {
}

private func rawHex() throws -> Bytes {
var bytes = Bytes()
for i in stride(from: 0, to: self.count, by: 2) {
let start = self.index(self.startIndex, offsetBy: i)
let end = self.index(self.startIndex, offsetBy: i + 2)
let charArray = Array(self)

guard let byte = Byte(String(self[start..<end]), radix: 16) else {
var bytes = Bytes()
for i in stride(from: 0, to: charArray.count, by: 2) {
guard let higher = hexMapping[charArray[i]], let lower = hexMapping[charArray[i + 1]] else {
throw StringHexBytesError.hexStringMalformed
}
let byte: UInt8 = (higher << 4) | lower
bytes.append(byte)
}

Expand Down
4 changes: 1 addition & 3 deletions Sources/Core/Transaction/EthereumAddress.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@ public struct EthereumAddress {
public func hex(eip55: Bool) -> String {
var hex = "0x"
if !eip55 {
for b in rawAddress {
hex += String(format: "%02x", b)
}
return rawAddress.hexString(prefix: true)
} else {
var address = ""
for b in rawAddress {
Expand Down

0 comments on commit 561c1ff

Please sign in to comment.