From e33de2e78aa8d2316dc11a27b039a51732908c53 Mon Sep 17 00:00:00 2001 From: 6od9i <6od911@gmail.com> Date: Wed, 15 Mar 2023 10:43:59 +0400 Subject: [PATCH 1/5] - ABIDecoding getting data slice in followTheData changed to using start data index --- Sources/Web3Core/EthereumABI/ABIDecoding.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIDecoding.swift b/Sources/Web3Core/EthereumABI/ABIDecoding.swift index cb220fec6..aface0d84 100755 --- a/Sources/Web3Core/EthereumABI/ABIDecoding.swift +++ b/Sources/Web3Core/EthereumABI/ABIDecoding.swift @@ -188,12 +188,12 @@ extension ABIDecoder { 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.indices.startIndex + Int(pointer) ..< data.indices.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.indices.startIndex + Int(pointer) ..< data.indices.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 From e37bdca685013d61bfcd0d16a0b4c0d0073ba9f1 Mon Sep 17 00:00:00 2001 From: 6od9i <6od911@gmail.com> Date: Wed, 15 Mar 2023 18:19:56 +0400 Subject: [PATCH 2/5] - Safe getting bounds from data slices in decoding added --- Sources/Web3Core/Contract/ContractProtocol.swift | 10 +++++----- Sources/Web3Core/EthereumABI/ABIElements.swift | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 06e3c01a7..b64d8c3da 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -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.indices.startIndex.. [String: Any]? { guard data.count % 32 == 4 else { return nil } - let methodSignature = data[0..<4].toHexString().addHexPrefix().lowercased() + let methodSignature = data[data.indices.startIndex .. ABI.Element.Function? { guard data.count >= 4 else { return nil } - return methods[data[0..<4].toHexString().addHexPrefix()]?.first + return methods[data[data.indices.startIndex..= 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.indices.startIndex..= 4, let errors = errors, - let customError = errors[data[0..<4].toHexString().stripHexPrefix()] { + let customError = errors[data[data.indices.startIndex + 0.. 32 && !customError.inputs.isEmpty), - let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[4.. Date: Wed, 15 Mar 2023 18:24:47 +0400 Subject: [PATCH 3/5] - boundses in data fixed --- Sources/Web3Core/EthereumABI/ABIElements.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 0f09a6fa7..0ceec904f 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -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[data.indices.startIndex..= 4, let errors = errors, - let customError = errors[data[data.indices.startIndex + 0.. 32 && !customError.inputs.isEmpty), - let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[data.indices.startIndex + 4.. Date: Wed, 15 Mar 2023 18:26:57 +0400 Subject: [PATCH 4/5] - boundses in contract protocol fixed --- Sources/Web3Core/Contract/ContractProtocol.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index b64d8c3da..42f54504b 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -210,8 +210,8 @@ extension ContractProtocol { func decodeInputData(_ data: Data) -> [String: Any]? { guard data.count >= 4 else { return nil } - let methodId = data[data.indices.startIndex.. [String: Any]? { guard data.count % 32 == 4 else { return nil } - let methodSignature = data[data.indices.startIndex .. ABI.Element.Function? { guard data.count >= 4 else { return nil } - return methods[data[data.indices.startIndex.. Date: Fri, 24 Mar 2023 16:32:01 +0400 Subject: [PATCH 5/5] - Test slices decoding added - PR fixed --- .../Web3Core/EthereumABI/ABIElements.swift | 2 +- .../localTests/ABIDecoderSliceTests.swift | 103 ++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index 0ceec904f..d2c7ab9b6 100755 --- a/Sources/Web3Core/EthereumABI/ABIElements.swift +++ b/Sources/Web3Core/EthereumABI/ABIElements.swift @@ -410,7 +410,7 @@ extension ABI.Element.Function { if data.count >= 4, let errors = errors, - let customError = errors[data[data.indices.startIndex + 0 ..< data.indices.startIndex + 4].toHexString().stripHexPrefix()] { + let customError = errors[data[data.indices.startIndex ..< data.indices.startIndex + 4].toHexString().stripHexPrefix()] { var errorResponse: [String: Any] = ["_success": false, "_abortedByRevertOrRequire": true, "_error": customError.errorDeclaration] if (data.count > 32 && !customError.inputs.isEmpty), diff --git a/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift new file mode 100644 index 000000000..a3fbeb43c --- /dev/null +++ b/Tests/web3swiftTests/localTests/ABIDecoderSliceTests.swift @@ -0,0 +1,103 @@ +// +// ABIDecoderSliceTests.swift +// localTests +// +// Created by 6od9i on 24.03.2023. +// + +import Foundation +import Web3Core +import XCTest +import BigInt +@testable import web3swift + +final class ABIDecoderSliceTests: XCTestCase { + func testBallancesDataSlice() throws { + /// Arrange + let balanceofMethod = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! + let correctValues = ["13667129429770787859", "3298264", "47475", "19959", "607690442193821", "999170411478050086"] + let hex6Responses = + "000000000000000000000000000000000000000000000000bdab65ce08c65c1300000000000000000000000000000000000000000000000000000000003253d8000000000000000000000000000000000000000000000000000000000000b9730000000000000000000000000000000000000000000000000000000000004df7000000000000000000000000000000000000000000000000000228b0f4f0bb9d0000000000000000000000000000000000000000000000000dddc432063ae526" + let data = Data(hex: hex6Responses) + let answerSize = 32 + var startIndex = 0 + var results = [String]() + + /// Act + while startIndex < data.count { + let slice = data[startIndex ..< startIndex + answerSize] + startIndex += answerSize + guard let bigInt = balanceofMethod.decodeReturnData(slice)["0"] as? BigUInt else { + throw Web3Error.processingError(desc: "Can not decode returned parameters") + } + let value = Utilities.formatToPrecision(bigInt, units: .wei) + results.append(value) + } + + /// Assert + XCTAssertEqual(correctValues, results) + } + + func testDecodeMulticallDifferentValues() async throws { + /// Arrange + let multiCall2Contract = try EthereumContract(Self.multiCall2, at: nil) + let differentRequestsContract = try EthereumContract(Self.differentRequestsContract, at: nil) + + let data = Data(hex: "0000000000000000000000000000000000000000000000000000000001980dd40000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000001af3f329e8be154074d8769d1ffa4ee058b1dbc3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000006358d8a5000000000000000000000000000000000000000000000000000000007628d02500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000212295b818158b400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000") + + let methods = [differentRequestsContract.methods["arrayValue"]?.first, + differentRequestsContract.methods["firstValue"]?.first, + differentRequestsContract.methods["secondValue"]?.first].compactMap({$0}) + + XCTAssertEqual(methods.count, 3) + + /// Act + guard let decodedData = multiCall2Contract.decodeReturnData("aggregate", data: data) else { + throw Web3Error.processingError(desc: "Can not decode returned parameters") + } + + guard let returnData = decodedData["returnData"] as? [Data] else { + throw Web3Error.dataError + } + + XCTAssertEqual(returnData.count, 3) + + for item in methods.enumerated() { + XCTAssertNotNil(item.element.decodeReturnData(returnData[item.offset])["0"]) + } + } + + func testDecodeMulticallCopy() throws { + /// Arrange + let data = Data(hex: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000004fe6dab4abca350650") + let contract = try EthereumContract(Self.multiCall2, at: nil) + let erc20_balanceof = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! + + /// Act + guard let decodedData = contract.decodeReturnData("tryAggregate", data: data) else { + throw Web3Error.processingError(desc: "Can not decode returned parameters") + } + + guard let returnData = decodedData["returnData"] as? [[Any]] else { + throw Web3Error.dataError + } + var resultArray = [BigUInt]() + for i in 0..<2 { + guard let data = returnData[i][1] as? Data, + let balance = erc20_balanceof.decodeReturnData(data)["0"] as? BigUInt else { + resultArray.append(0) + continue + } + resultArray.append(balance) + } + + /// Assert + XCTAssert(resultArray.count == 2) + } +} + +extension ABIDecoderSliceTests { + public static let differentRequestsContract = "[{\"inputs\":[],\"name\":\"firstValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"arrayValue\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"enum IInstanceV1.Period\",\"name\":\"period\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"startTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"endTime\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"secondValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]" + + public static let multiCall2 = "[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"aggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"returnData\",\"type\":\"bytes[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"blockAndAggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getBlockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockCoinbase\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"coinbase\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockDifficulty\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"difficulty\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"gaslimit\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCurrentBlockTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"getEthBalance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getLastBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"tryAggregate\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\"}],\"name\":\"tryBlockAndAggregate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"returnData\",\"type\":\"bytes\"}],\"internalType\":\"struct Multicall2.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" +}