Skip to content

Commit

Permalink
Merge pull request #808 from web3swift-team/fix/abi-decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
yaroslavyaroslav authored Apr 2, 2023
2 parents 241e009 + d397626 commit d5cf381
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 31 deletions.
10 changes: 5 additions & 5 deletions Sources/Web3Core/Contract/ContractProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ extension ContractProtocol {

func decodeInputData(_ data: Data) -> [String: Any]? {
guard data.count >= 4 else { return nil }
let methodId = data[0..<4].toHexString()
let data = data[4...]
let methodId = data[data.startIndex ..< data.startIndex + 4].toHexString()
let data = data[(data.startIndex + 4)...]
return decodeInputData(methodId, data: data)
}
}
Expand Down Expand Up @@ -333,14 +333,14 @@ extension DefaultContractProtocol {

public func decodeInputData(_ data: Data) -> [String: Any]? {
guard data.count % 32 == 4 else { return nil }
let methodSignature = data[0..<4].toHexString().addHexPrefix().lowercased()
let methodSignature = data[data.startIndex ..< data.startIndex + 4].toHexString().addHexPrefix().lowercased()

guard let function = methods[methodSignature]?.first else { return nil }
return function.decodeInputData(Data(data[4 ..< data.count]))
return function.decodeInputData(Data(data[data.startIndex + 4 ..< data.startIndex + data.count]))
}

public func getFunctionCalled(_ data: Data) -> ABI.Element.Function? {
guard data.count >= 4 else { return nil }
return methods[data[0..<4].toHexString().addHexPrefix()]?.first
return methods[data[data.startIndex ..< data.startIndex + 4].toHexString().addHexPrefix()]?.first
}
}
44 changes: 23 additions & 21 deletions Sources/Web3Core/EthereumABI/ABIDecoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,28 @@ extension ABIDecoder {
guard let elementItself = elData, let nextElementPointer = nextPtr else {
return (nil, nil)
}
let startIndex = UInt64(elementItself.startIndex)
switch type {
case .uint(let bits):
guard elementItself.count >= 32 else {break}
let mod = BigUInt(1) << bits
let dataSlice = elementItself[0 ..< 32]
let dataSlice = elementItself[startIndex ..< startIndex + 32]
let v = BigUInt(dataSlice) % mod
return (v, type.memoryUsage)
case .int(let bits):
guard elementItself.count >= 32 else {break}
let mod = BigInt(1) << bits
let dataSlice = elementItself[0 ..< 32]
let dataSlice = elementItself[startIndex ..< startIndex + 32]
let v = BigInt.fromTwosComplement(data: dataSlice) % mod
return (v, type.memoryUsage)
case .address:
guard elementItself.count >= 32 else {break}
let dataSlice = elementItself[12 ..< 32]
let dataSlice = elementItself[startIndex + 12 ..< startIndex + 32]
let address = EthereumAddress(dataSlice)
return (address, type.memoryUsage)
case .bool:
guard elementItself.count >= 32 else {break}
let dataSlice = elementItself[0 ..< 32]
let dataSlice = elementItself[startIndex ..< startIndex + 32]
let v = BigUInt(dataSlice)
if v == BigUInt(36) ||
v == BigUInt(32) ||
Expand All @@ -69,33 +70,33 @@ extension ABIDecoder {
}
case .bytes(let length):
guard elementItself.count >= 32 else {break}
let dataSlice = elementItself[0 ..< length]
return (dataSlice, type.memoryUsage)
let dataSlice = elementItself[startIndex ..< startIndex + length]
return (Data(dataSlice), type.memoryUsage)
case .string:
guard elementItself.count >= 32 else {break}
var dataSlice = elementItself[0 ..< 32]
var dataSlice = elementItself[startIndex ..< startIndex + 32]
let length = UInt64(BigUInt(dataSlice))
guard elementItself.count >= 32+length else {break}
guard elementItself.count >= 32 + length else {break}
dataSlice = elementItself[32 ..< 32 + length]
guard let string = String(data: dataSlice, encoding: .utf8) else {break}
return (string, type.memoryUsage)
case .dynamicBytes:
guard elementItself.count >= 32 else {break}
var dataSlice = elementItself[0 ..< 32]
var dataSlice = elementItself[startIndex ..< startIndex + 32]
let length = UInt64(BigUInt(dataSlice))
guard elementItself.count >= 32+length else {break}
dataSlice = elementItself[32 ..< 32 + length]
return (dataSlice, nextElementPointer)
guard elementItself.count >= 32 + length else {break}
dataSlice = elementItself[startIndex + 32 ..< startIndex + 32 + length]
return (Data(dataSlice), nextElementPointer)
case .array(type: let subType, length: let length):
switch type.arraySize {
case .dynamicSize:
if subType.isStatic {
// uint[] like, expect length and elements
guard elementItself.count >= 32 else {break}
var dataSlice = elementItself[0 ..< 32]
var dataSlice = elementItself[startIndex ..< startIndex + 32]
let length = UInt64(BigUInt(dataSlice))
guard elementItself.count >= 32 + subType.memoryUsage*length else {break}
dataSlice = elementItself[32 ..< 32 + subType.memoryUsage*length]
dataSlice = elementItself[startIndex + 32 ..< startIndex + 32 + subType.memoryUsage*length]
var subpointer: UInt64 = 32
var toReturn = [Any]()
for _ in 0 ..< length {
Expand All @@ -108,10 +109,10 @@ extension ABIDecoder {
} else {
// in principle is true for tuple[], so will work for string[] too
guard elementItself.count >= 32 else {break}
var dataSlice = elementItself[0 ..< 32]
var dataSlice = elementItself[startIndex ..< startIndex + 32]
let length = UInt64(BigUInt(dataSlice))
guard elementItself.count >= 32 else {break}
dataSlice = Data(elementItself[32 ..< elementItself.count])
dataSlice = Data(elementItself[startIndex + 32 ..< UInt64(elementItself.count)])
var subpointer: UInt64 = 0
var toReturn = [Any]()
for _ in 0 ..< length {
Expand Down Expand Up @@ -179,21 +180,21 @@ extension ABIDecoder {
}
case .function:
guard elementItself.count >= 32 else {break}
let dataSlice = elementItself[8 ..< 32]
return (dataSlice, type.memoryUsage)
let dataSlice = elementItself[startIndex + 8 ..< startIndex + 32]
return (Data(dataSlice), type.memoryUsage)
}
return (nil, nil)
}

fileprivate static func followTheData(type: ABI.Element.ParameterType, data: Data, pointer: UInt64 = 0) -> (elementEncoding: Data?, nextElementPointer: UInt64?) {
if type.isStatic {
guard data.count >= pointer + type.memoryUsage else {return (nil, nil)}
let elementItself = data[pointer ..< pointer + type.memoryUsage]
let elementItself = data[data.startIndex + Int(pointer) ..< data.startIndex + Int(pointer + type.memoryUsage)]
let nextElement = pointer + type.memoryUsage
return (Data(elementItself), nextElement)
} else {
guard data.count >= pointer + type.memoryUsage else {return (nil, nil)}
let dataSlice = data[pointer ..< pointer + type.memoryUsage]
let dataSlice = data[data.startIndex + Int(pointer) ..< data.startIndex + Int(pointer + type.memoryUsage)]
let bn = BigUInt(dataSlice)
if bn > UInt64.max || bn >= data.count {
// there are ERC20 contracts that use bytes32 instead of string. Let's be optimistic and return some data
Expand All @@ -209,7 +210,8 @@ extension ABIDecoder {
return (nil, nil)
}
let elementPointer = UInt64(bn)
let elementItself = data[elementPointer ..< UInt64(data.count)]
let startIndex = UInt64(data.startIndex)
let elementItself = data[startIndex + elementPointer ..< startIndex + UInt64(data.count)]
let nextElement = pointer + type.memoryUsage
return (Data(elementItself), nextElement)
}
Expand Down
10 changes: 5 additions & 5 deletions Sources/Web3Core/EthereumABI/ABIElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,9 @@ extension ABI.Element.Function {
/// 4) `messageLength` is used to determine where message bytes end to decode string correctly.
/// 5) The rest of the `data` must be 0 bytes or empty.
if data.bytes.count >= 100,
Data(data[0..<4]) == Data.fromHex("08C379A0"),
BigInt(data[4..<36]) == 32,
let messageLength = Int(Data(data[36..<68]).toHexString(), radix: 16),
Data(data[data.startIndex ..< data.startIndex + 4]) == Data.fromHex("08C379A0"),
BigInt(data[data.startIndex + 4 ..< data.startIndex + 36]) == 32,
let messageLength = Int(Data(data[data.startIndex + 36 ..< data.startIndex + 68]).toHexString(), radix: 16),
let message = String(bytes: data.bytes[68..<(68+messageLength)], encoding: .utf8),
(68+messageLength == data.count || data.bytes[68+messageLength..<data.count].reduce(0) { $0 + $1 } == 0) {
return ["_success": false,
Expand All @@ -410,11 +410,11 @@ extension ABI.Element.Function {

if data.count >= 4,
let errors = errors,
let customError = errors[data[0..<4].toHexString().stripHexPrefix()] {
let customError = errors[data[data.startIndex ..< data.startIndex + 4].toHexString().stripHexPrefix()] {
var errorResponse: [String: Any] = ["_success": false, "_abortedByRevertOrRequire": true, "_error": customError.errorDeclaration]

if (data.count > 32 && !customError.inputs.isEmpty),
let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[4..<data.count])) {
let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[data.startIndex + 4 ..< data.startIndex + data.count])) {
for idx in decodedInputs.indices {
errorResponse["\(idx)"] = decodedInputs[idx]
if !customError.inputs[idx].name.isEmpty {
Expand Down
Loading

0 comments on commit d5cf381

Please sign in to comment.