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 01/15] - 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 02/15] - 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 03/15] - 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 04/15] - 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: Thu, 16 Mar 2023 18:44:05 +0800 Subject: [PATCH 05/15] return a new instance of Data in decodeSingleType --- 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..e5984c81a 100755 --- a/Sources/Web3Core/EthereumABI/ABIDecoding.swift +++ b/Sources/Web3Core/EthereumABI/ABIDecoding.swift @@ -70,7 +70,7 @@ extension ABIDecoder { case .bytes(let length): guard elementItself.count >= 32 else {break} let dataSlice = elementItself[0 ..< length] - return (dataSlice, type.memoryUsage) + return (Data(dataSlice), type.memoryUsage) case .string: guard elementItself.count >= 32 else {break} var dataSlice = elementItself[0 ..< 32] @@ -85,7 +85,7 @@ extension ABIDecoder { let length = UInt64(BigUInt(dataSlice)) guard elementItself.count >= 32+length else {break} dataSlice = elementItself[32 ..< 32 + length] - return (dataSlice, nextElementPointer) + return (Data(dataSlice), nextElementPointer) case .array(type: let subType, length: let length): switch type.arraySize { case .dynamicSize: From 93af1e8fb90ededecc798fca8c1f46116d9e3720 Mon Sep 17 00:00:00 2001 From: august Date: Thu, 16 Mar 2023 19:40:00 +0800 Subject: [PATCH 06/15] add decode multicall test --- .../localTests/ABIDecoderTests.swift | 370 ++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 Tests/web3swiftTests/localTests/ABIDecoderTests.swift diff --git a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift new file mode 100644 index 000000000..9c563f018 --- /dev/null +++ b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift @@ -0,0 +1,370 @@ +// +// ABIDecoderTests.swift +// +// +// Created by liugang zhang on 2023/3/16. +// + +import Foundation +import Web3Core +import XCTest +import BigInt +@testable import web3swift + +final class ABIDecoderTests: XCTestCase { + + func testDecodeMulticall() throws { + // get result from http + // + // let requests = tokenAddress.map { address -> AnyObject in + // let callData = erc20_balanceof.encodeParameters([account as NSString]) + // return [address, callData as Any] as AnyObject + // } as AnyObject + // + // let read = contract.createReadOperation( + // "tryAggregate", + // parameters: [false, requests] as [AnyObject] + // ) + // let results = try await read?.callContractMethod() + + let data = Data(hex: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000004fe6dab4abca350650") + let contract = try EthereumContract(Self.multiCall2, at: nil) + let erc20_balanceof = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! + guard let decodedData = contract.decodeReturnData("tryAggregate", data: data) else { + throw Web3Error.processingError(desc: "Can not decode returned parameters") + } + + guard let returnData = decodedData["returnData"] as? Array> 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) + } + print(resultArray) + XCTAssert(resultArray.count == 2) + } + + 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" + } + ] + """ +} From 15ea9690d25ae862888454e4aa62a68ac73c184f Mon Sep 17 00:00:00 2001 From: august Date: Thu, 16 Mar 2023 20:07:05 +0800 Subject: [PATCH 07/15] fix lint --- Tests/web3swiftTests/localTests/ABIDecoderTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift index 9c563f018..cd565c9a9 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift @@ -34,7 +34,7 @@ final class ABIDecoderTests: XCTestCase { throw Web3Error.processingError(desc: "Can not decode returned parameters") } - guard let returnData = decodedData["returnData"] as? Array> else { + guard let returnData = decodedData["returnData"] as? [[Any]] else { throw Web3Error.dataError } var resultArray = [BigUInt]() From a6eb0c32f50618251ab04972711681db17bb7de5 Mon Sep 17 00:00:00 2001 From: august Date: Thu, 16 Mar 2023 20:37:37 +0800 Subject: [PATCH 08/15] minify multicall contract string --- .../localTests/ABIDecoderTests.swift | 320 +----------------- 1 file changed, 2 insertions(+), 318 deletions(-) diff --git a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift index cd565c9a9..278e976d8 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift @@ -1,6 +1,6 @@ // // ABIDecoderTests.swift -// +// // // Created by liugang zhang on 2023/3/16. // @@ -50,321 +50,5 @@ final class ABIDecoderTests: XCTestCase { XCTAssert(resultArray.count == 2) } - 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" - } - ] - """ + 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\"}]" } From 9683e3e7f2860a56304f5aa77366a74b6ca9a4cb Mon Sep 17 00:00:00 2001 From: august Date: Fri, 24 Mar 2023 18:49:11 +0800 Subject: [PATCH 09/15] remove comments --- .../localTests/ABIDecoderTests.swift | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift index 278e976d8..1d4ae0524 100644 --- a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift +++ b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift @@ -14,19 +14,6 @@ import BigInt final class ABIDecoderTests: XCTestCase { func testDecodeMulticall() throws { - // get result from http - // - // let requests = tokenAddress.map { address -> AnyObject in - // let callData = erc20_balanceof.encodeParameters([account as NSString]) - // return [address, callData as Any] as AnyObject - // } as AnyObject - // - // let read = contract.createReadOperation( - // "tryAggregate", - // parameters: [false, requests] as [AnyObject] - // ) - // let results = try await read?.callContractMethod() - let data = Data(hex: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000004fe6dab4abca350650") let contract = try EthereumContract(Self.multiCall2, at: nil) let erc20_balanceof = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! @@ -46,9 +33,10 @@ final class ABIDecoderTests: XCTestCase { } resultArray.append(balance) } - print(resultArray) XCTAssert(resultArray.count == 2) } +} +extension ABIDecoderTests { 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\"}]" } From 3b28de62b5486086d298c1cb60c2da1b7350f36c Mon Sep 17 00:00:00 2001 From: 6od9i <6od911@gmail.com> Date: Fri, 24 Mar 2023 16:32:01 +0400 Subject: [PATCH 10/15] - 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\"}]" +} From bfcf02829a10f087fe2c9c739155cb49d511f139 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sat, 1 Apr 2023 01:38:39 +0300 Subject: [PATCH 11/15] fix: moved test to a different file --- .../localTests/ABIDecoderTests.swift | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 Tests/web3swiftTests/localTests/ABIDecoderTests.swift diff --git a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift b/Tests/web3swiftTests/localTests/ABIDecoderTests.swift deleted file mode 100644 index 1d4ae0524..000000000 --- a/Tests/web3swiftTests/localTests/ABIDecoderTests.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ABIDecoderTests.swift -// -// -// Created by liugang zhang on 2023/3/16. -// - -import Foundation -import Web3Core -import XCTest -import BigInt -@testable import web3swift - -final class ABIDecoderTests: XCTestCase { - - func testDecodeMulticall() throws { - let data = Data(hex: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000004fe6dab4abca350650") - let contract = try EthereumContract(Self.multiCall2, at: nil) - let erc20_balanceof = try EthereumContract(Web3.Utils.erc20ABI).methods["balanceOf"]!.first! - 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) - } - XCTAssert(resultArray.count == 2) - } -} - -extension ABIDecoderTests { - 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\"}]" -} From 8cd4c44a30dda2d422cbc0235c39702372750d6d Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sat, 1 Apr 2023 01:39:17 +0300 Subject: [PATCH 12/15] fix: using Data.startIndex to safely handle decoding of Data and Data slices --- .../Web3Core/EthereumABI/ABIDecoding.swift | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Sources/Web3Core/EthereumABI/ABIDecoding.swift b/Sources/Web3Core/EthereumABI/ABIDecoding.swift index f9ecae472..0ae82695b 100755 --- a/Sources/Web3Core/EthereumABI/ABIDecoding.swift +++ b/Sources/Web3Core/EthereumABI/ABIDecoding.swift @@ -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) || @@ -69,22 +70,22 @@ extension ABIDecoder { } case .bytes(let length): guard elementItself.count >= 32 else {break} - let dataSlice = elementItself[0 ..< length] + 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] + 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 { @@ -92,10 +93,10 @@ extension ABIDecoder { 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 { @@ -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 { @@ -179,7 +180,7 @@ extension ABIDecoder { } case .function: guard elementItself.count >= 32 else {break} - let dataSlice = elementItself[8 ..< 32] + let dataSlice = elementItself[startIndex + 8 ..< startIndex + 32] return (dataSlice, type.memoryUsage) } return (nil, nil) @@ -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) } From ffbf4e7a55bf1b6bc11c94e2ce8944dfcf49ed9b Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sun, 2 Apr 2023 10:57:33 +0300 Subject: [PATCH 13/15] chore: replaced data.indices.startIndex with data.startIndex --- Sources/Web3Core/Contract/ContractProtocol.swift | 12 ++++++------ Sources/Web3Core/EthereumABI/ABIDecoding.swift | 4 ++-- Sources/Web3Core/EthereumABI/ABIElements.swift | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 42f54504b..3dd0b5c1d 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 ..< data.indices.startIndex + 4].toHexString() - let data = data[(data.indices.startIndex + 4)...] + let methodId = data[data.startIndex ..< data.startIndex + 4].toHexString() + let data = data[(data.startIndex + 4)...] return decodeInputData(methodId, data: data) } } @@ -326,21 +326,21 @@ extension DefaultContractProtocol { if method == "fallback" { return nil } - return methods[method]?.compactMap({ function in + return methods[method]?.compactMap({ functio n in return function.decodeInputData(data) }).first } public func decodeInputData(_ data: Data) -> [String: Any]? { guard data.count % 32 == 4 else { return nil } - let methodSignature = data[data.indices.startIndex ..< data.indices.startIndex + 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[data.indices.startIndex + 4 ..< data.indices.startIndex + 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[data.indices.startIndex ..< data.indices.startIndex + 4].toHexString().addHexPrefix()]?.first + return methods[data[data.startIndex ..< data.startIndex + 4].toHexString().addHexPrefix()]?.first } } diff --git a/Sources/Web3Core/EthereumABI/ABIDecoding.swift b/Sources/Web3Core/EthereumABI/ABIDecoding.swift index 0ae82695b..d7cc65605 100755 --- a/Sources/Web3Core/EthereumABI/ABIDecoding.swift +++ b/Sources/Web3Core/EthereumABI/ABIDecoding.swift @@ -189,12 +189,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[data.indices.startIndex + Int(pointer) ..< data.indices.startIndex + Int(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[data.indices.startIndex + Int(pointer) ..< data.indices.startIndex + Int(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 diff --git a/Sources/Web3Core/EthereumABI/ABIElements.swift b/Sources/Web3Core/EthereumABI/ABIElements.swift index d2c7ab9b6..5dca0b331 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 ..< data.indices.startIndex + 4]) == Data.fromHex("08C379A0"), - BigInt(data[data.indices.startIndex + 4 ..< data.indices.startIndex + 36]) == 32, - let messageLength = Int(Data(data[data.indices.startIndex + 36 ..< data.indices.startIndex + 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..= 4, let errors = errors, - let customError = errors[data[data.indices.startIndex ..< data.indices.startIndex + 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[data.indices.startIndex + 4 ..< data.indices.startIndex + 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 { From 9c28cae35c2eb6d7c064d5254121cd1af53b237c Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sun, 2 Apr 2023 11:02:39 +0300 Subject: [PATCH 14/15] fix: argument name fix --- Sources/Web3Core/Contract/ContractProtocol.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/Contract/ContractProtocol.swift b/Sources/Web3Core/Contract/ContractProtocol.swift index 3dd0b5c1d..43a4c1543 100755 --- a/Sources/Web3Core/Contract/ContractProtocol.swift +++ b/Sources/Web3Core/Contract/ContractProtocol.swift @@ -326,7 +326,7 @@ extension DefaultContractProtocol { if method == "fallback" { return nil } - return methods[method]?.compactMap({ functio n in + return methods[method]?.compactMap({ function in return function.decodeInputData(data) }).first } From d39762613de43aae2ca366bbee8ac532bafa5b24 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Sun, 2 Apr 2023 11:08:22 +0300 Subject: [PATCH 15/15] chore: wrapped data slice into Data --- Sources/Web3Core/EthereumABI/ABIDecoding.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Web3Core/EthereumABI/ABIDecoding.swift b/Sources/Web3Core/EthereumABI/ABIDecoding.swift index d7cc65605..5fb9df99b 100755 --- a/Sources/Web3Core/EthereumABI/ABIDecoding.swift +++ b/Sources/Web3Core/EthereumABI/ABIDecoding.swift @@ -181,7 +181,7 @@ extension ABIDecoder { case .function: guard elementItself.count >= 32 else {break} let dataSlice = elementItself[startIndex + 8 ..< startIndex + 32] - return (dataSlice, type.memoryUsage) + return (Data(dataSlice), type.memoryUsage) } return (nil, nil) }