Skip to content

Commit

Permalink
fix: rigorous testing for abi decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
koraykoska committed Oct 21, 2022
1 parent 4fee785 commit 7dbf9c1
Show file tree
Hide file tree
Showing 7 changed files with 1,012 additions and 532 deletions.
56 changes: 35 additions & 21 deletions Sources/ContractABI/ABI/ABIDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ public struct ABIDecoder {

// MARK: - Decoding

public static func decodeTuple(_ type: SolidityType, from hexString: String) throws -> Any {
if let decoded = try decodeTuple([type], from: hexString).first {
public static func decodeTuple(_ type: SolidityType, from hexString: String, repeatingComponents: [SolidityParameter]? = nil) throws -> Any {
if let decoded = try decodeTuple([type], from: hexString, repeatingComponents: repeatingComponents).first {
return decoded
}
throw Error.unknownError
}

public static func decodeTuple(_ types: SolidityType..., from hexString: String) throws -> [Any] {
return try decodeTuple(types, from: hexString)
public static func decodeTuple(_ types: SolidityType..., from hexString: String, repeatingComponents: [SolidityParameter]? = nil) throws -> [Any] {
return try decodeTuple(types, from: hexString, repeatingComponents: repeatingComponents)
}

public static func decodeTuple(_ types: [SolidityType], from hexString: String) throws -> [Any] {
public static func decodeTuple(_ types: [SolidityType], from hexString: String, repeatingComponents: [SolidityParameter]? = nil) throws -> [Any] {
struct BasicSolParam: SolidityParameter {
let name: String
let type: SolidityType
Expand All @@ -46,7 +46,7 @@ public struct ABIDecoder {

var outputs = [SolidityParameter]()
for i in 0..<types.count {
outputs.append(BasicSolParam(name: "\(i)", type: types[i], components: nil))
outputs.append(BasicSolParam(name: "\(i)", type: types[i], components: repeatingComponents))
}

let decodedDictionary = try decodeTuple(outputs: outputs, from: hexString)
Expand All @@ -70,6 +70,21 @@ public struct ABIDecoder {

var returnDictionary: [String: Any] = [:]

if outputs.count == 1 {
switch outputs[0].type {
case .array(let type, let length):
// Single static length non-dynamic arrays decode like a tuple without a dataLocation
if let _ = length, !type.isDynamic {
let decodedArray = try decodeType(type: outputs[0].type, hexString: hexString, components: outputs[0].components)
returnDictionary[outputs[0].name] = decodedArray

return returnDictionary
}
default:
break
}
}

var currentIndex = 0
var tailsToBeParsed: [(dataLocation: Int, param: SolidityParameter)] = []
for i in 0..<outputs.count {
Expand Down Expand Up @@ -114,6 +129,10 @@ public struct ABIDecoder {
let missingTails = tailsToBeParsed.map({ $0.param })

for i in 0..<missingTails.count {
if endIndexes[i] < startIndexes[i] {
throw Error.couldNotDecodeType(type: missingTails[i].type, string: "unexpected format of abi encoding")
}

let output = missingTails[i]

// Index to start from in hex
Expand Down Expand Up @@ -159,7 +178,7 @@ public struct ABIDecoder {
throw Error.associatedTypeNotFound(type: type)
}
case .array(let elementType, let length):
return try decodeArray(elementType: elementType, length: length, from: hexString)
return try decodeArray(elementType: elementType, length: length, from: hexString, components: components)
case .tuple(let types):
if let components = components {
// will return with names
Expand All @@ -173,34 +192,29 @@ public struct ABIDecoder {

// MARK: - Arrays

private static func decodeArray(elementType: SolidityType, length: UInt?, from hexString: String) throws -> [Any] {
if !elementType.isDynamic, let length = length {
return try decodeFixedArray(elementType: elementType, length: Int(length), from: hexString)
private static func decodeArray(elementType: SolidityType, length: UInt?, from hexString: String, components: [SolidityParameter]?) throws -> [Any] {
if let length = length {
return try decodeFixedLengthArray(elementType: elementType, length: Int(length), from: hexString, components: components)
} else {
return try decodeDynamicArray(elementType: elementType, from: hexString)
return try decodeDynamicLengthArray(elementType: elementType, from: hexString, components: components)
}
}

private static func decodeDynamicArray(elementType: SolidityType, from hexString: String) throws -> [Any] {
private static func decodeDynamicLengthArray(elementType: SolidityType, from hexString: String, components: [SolidityParameter]?) throws -> [Any] {
// split into parts
let lengthString = hexString.substr(0, 64)
let valueString = String(hexString.dropFirst(64))
// calculate length
guard let string = lengthString, let length = Int(string, radix: 16) else {
throw Error.couldNotParseLength
}
return try decodeFixedArray(elementType: elementType, length: length, from: valueString)
return try decodeFixedLengthArray(elementType: elementType, length: length, from: valueString, components: components)
}

private static func decodeFixedArray(elementType: SolidityType, length: Int, from hexString: String) throws -> [Any] {
private static func decodeFixedLengthArray(elementType: SolidityType, length: Int, from hexString: String, components: [SolidityParameter]?) throws -> [Any] {
guard length > 0 else { return [] }
let elementSize = hexString.count / length
return try (0..<length).compactMap { n in
if let elementString = hexString.substr(n * elementSize, elementSize) {
return try decodeType(type: elementType, hexString: elementString)
}
return nil
}

return try decodeTuple([SolidityType](repeating: elementType, count: length), from: hexString, repeatingComponents: components)
}

// MARK: Event Values
Expand Down
8 changes: 4 additions & 4 deletions Tests/Web3Tests/ABITests/ABITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class ABITests: QuickSpec {

it("should decode array of dynamic elements") {
do {
let string = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000036162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000364656600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003676869000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036a6b6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036d6e6f0000000000000000000000000000000000000000000000000000000000"
let string = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000036162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000364656600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003676869000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036a6b6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036d6e6f0000000000000000000000000000000000000000000000000000000000"
let test: [String]? = try ABI.decodeParameters(types: [.array(type: .string, length: nil)], from: string).first as? [String]
let expected: [String] = ["abc", "def", "ghi", "jkl", "mno"]
expect(test).to(equal(expected))
Expand All @@ -263,7 +263,7 @@ class ABITests: QuickSpec {

it("should decode nested array") {
do {
let string = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006"
let string = "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006"
let test: [[UInt32]]? = try ABI.decodeParameters(types: [.array(type: .array(type: .uint32, length: nil), length: nil)], from: string).first as? [[UInt32]]
let expected: [[UInt32]] = [[1,2,3], [4,5,6]]
expect(test).to(equal(expected))
Expand All @@ -289,7 +289,7 @@ class ABITests: QuickSpec {

it("should decode fixed array of dynamic type") {
do {
let string = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000036162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000364656600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003676869000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036a6b6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036d6e6f0000000000000000000000000000000000000000000000000000000000"
let string = "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000036162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000364656600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003676869000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036a6b6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000036d6e6f0000000000000000000000000000000000000000000000000000000000"
let test = try ABI.decodeParameters(types: [.array(type: .string, length: 5)], from: string).first as? [String]
let expected = ["abc", "def", "ghi", "jkl", "mno"]
expect(test).to(equal(expected))
Expand All @@ -300,7 +300,7 @@ class ABITests: QuickSpec {

it("should decode nested dynamic array") {
do {
let string = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006"
let string = "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006"
let test: [[UInt32]]? = try ABI.decodeParameters(types: [.array(type: .array(type: .uint32, length: nil), length: 2)], from: string).first as? [[UInt32]]
let expected: [[UInt32]] = [[1,2,3], [4,5,6]]
expect(test).to(equal(expected))
Expand Down
42 changes: 42 additions & 0 deletions Tests/Web3Tests/ABITests/SolidityTypeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,47 @@ class SolidityTypeTests: XCTestCase {
subTypes: [.int16, .int16, .int24, .int24, .uint24, .uint160, .uint128]
)
XCTAssertEqual(singleTupleType, .array(type: .tuple([.int16, .int16, .int24, .int24, .uint24, .uint160, .uint128]), length: nil))

let deepTuple = SolidityType(
"tuple[]",
subTypes: [.int16, .int16, .int24, .tuple([.tuple([.uint24]), .tuple([.int16]), .string]), .uint24, .uint160, .uint128, .tuple([.bytes(length: nil)])]
)
XCTAssertEqual(deepTuple, .array(type: .tuple([.int16, .int16, .int24, .tuple([.tuple([.uint24]), .tuple([.int16]), .string]), .uint24, .uint160, .uint128, .tuple([.bytes(length: nil)])]), length: nil))

let twoDTuple = SolidityType(
"tuple[][]",
subTypes: [.int16, .int16, .int24, .tuple([.tuple([.uint24]), .tuple([.int16]), .string]), .uint24, .uint160, .uint128, .tuple([.bytes(length: nil)])]
)
XCTAssertEqual(twoDTuple, .array(type:
.array(type:
.tuple([
.int16,
.int16,
.int24,
.tuple([.tuple([.uint24]), .tuple([.int16]), .string]),
.uint24,
.uint160,
.uint128,
.tuple([.bytes(length: nil)])
]),
length: nil), length: nil))

let threeDFixedTupleArray = SolidityType(
"tuple[3][2][10]",
subTypes: [.int16, .int16, .int24, .tuple([.tuple([.uint24]), .tuple([.int16]), .string]), .uint24, .uint160, .uint128, .tuple([.bytes(length: nil)])]
)
XCTAssertEqual(threeDFixedTupleArray, .array(type: .array(type:
.array(type:
.tuple([
.int16,
.int16,
.int24,
.tuple([.tuple([.uint24]), .tuple([.int16]), .string]),
.uint24,
.uint160,
.uint128,
.tuple([.bytes(length: nil)])
]),
length: 3), length: 2), length: 10))
}
}
Loading

0 comments on commit 7dbf9c1

Please sign in to comment.