From acc38225b2ca311426842c9737275dacbf273a47 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Tue, 14 Feb 2023 14:25:43 +0700 Subject: [PATCH 1/2] Support multicall v2 --- web3swift/src/Multicall/Multicall.swift | 64 +++++++++++++++++++ .../src/Multicall/MulticallContract.swift | 32 ++++++++++ 2 files changed, 96 insertions(+) diff --git a/web3swift/src/Multicall/Multicall.swift b/web3swift/src/Multicall/Multicall.swift index 4b62546e..39e78afc 100644 --- a/web3swift/src/Multicall/Multicall.swift +++ b/web3swift/src/Multicall/Multicall.swift @@ -37,6 +37,21 @@ public struct Multicall { throw MulticallError.executionFailed(error) } } + + public func tryAggregate(requireSuccess: Bool, calls: [Call]) async throws -> Multicall.Multicall2Response { + let function = Contract.Functions.tryAggregate(contract: Contract.multicall2Address, requireSuccess: requireSuccess, calls: calls) + + do { + let data = try await function.call(withClient: client, responseType: Multicall2Response.self) + zip(calls, data.outputs) + .forEach { call, output in + try? call.handler?(output) + } + return data + } catch { + throw MulticallError.executionFailed(error) + } + } } extension Multicall { @@ -50,6 +65,16 @@ extension Multicall { } } } + public func tryAggregate(requireSuccess: Bool, calls: [Call], completionHandler: @escaping (Result) -> Void) { + Task { + do { + let res = try await tryAggregate(requireSuccess: requireSuccess, calls: calls) + completionHandler(.success(res)) + } catch let error as MulticallError { + completionHandler(.failure(error)) + } + } + } } extension Multicall { @@ -84,6 +109,45 @@ extension Multicall { } } } + + public struct Multicall2Result: ABITuple { + public static var types: [ABIType.Type] = [Bool.self, String.self] + public var encodableValues: [ABIType] { [success, returnData] } + + public let success: Bool + public let returnData: String + + public init?(values: [ABIDecoder.DecodedValue]) throws { + self.success = try values[0].decoded() + self.returnData = try values[1].decoded() + } + + public func encode(to encoder: ABIFunctionEncoder) throws { + try encoder.encode(success) + try encoder.encode(returnData) + } + + public init(success: Bool, returnData: String) { + self.success = success + self.returnData = returnData + } + } + + public struct Multicall2Response: ABIResponse { + static let multicallFailedError = "MULTICALL_FAIL".web3.keccak256.web3.hexString + public static var types: [ABIType.Type] = [ABIArray.self] + public let outputs: [Output] + + public init?(values: [ABIDecoder.DecodedValue]) throws { + let results: [Multicall2Result] = try values[0].decodedTupleArray() + self.outputs = results.map { result in + guard result.returnData != Self.multicallFailedError else { + return .failure(.contractFailure) + } + return .success(result.returnData) + } + } + } public struct Call: ABITuple { public static var types: [ABIType.Type] = [EthereumAddress.self, Data.self] diff --git a/web3swift/src/Multicall/MulticallContract.swift b/web3swift/src/Multicall/MulticallContract.swift index b740cf0e..89fdb4dc 100644 --- a/web3swift/src/Multicall/MulticallContract.swift +++ b/web3swift/src/Multicall/MulticallContract.swift @@ -10,6 +10,7 @@ extension Multicall { public enum Contract { static let goerliAddress: EthereumAddress = "0x77dCa2C955b15e9dE4dbBCf1246B4B85b651e50e" static let mainnetAddress: EthereumAddress = "0xF34D2Cb31175a51B23fb6e08cA06d7208FaD379F" + static let multicall2Address: EthereumAddress = "0x5ba1e12693dc8f9c48aad8770482f4739beed696" public static func registryAddress(for network: EthereumNetwork) -> EthereumAddress? { switch network { @@ -49,6 +50,37 @@ extension Multicall { try encoder.encode(calls) } } + + public struct tryAggregate: ABIFunction { + public static let name = "tryAggregate" + public let gasPrice: BigUInt? + public let gasLimit: BigUInt? + public var contract: EthereumAddress + public let from: EthereumAddress? + public let requireSuccess: Bool + public let calls: [Call] + + public init( + contract: EthereumAddress, + from: EthereumAddress? = nil, + gasPrice: BigUInt? = nil, + gasLimit: BigUInt? = nil, + requireSuccess: Bool, + calls: [Call] + ) { + self.contract = contract + self.gasPrice = gasPrice + self.gasLimit = gasLimit + self.from = from + self.requireSuccess = requireSuccess + self.calls = calls + } + + public func encode(to encoder: ABIFunctionEncoder) throws { + try encoder.encode(requireSuccess) + try encoder.encode(calls) + } + } } } } From d372a5fddc4c6ade801956161418db4fdf8f1838 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Tue, 14 Feb 2023 21:33:26 +0700 Subject: [PATCH 2/2] Resolve linting issues and add tests --- web3sTests/Multicall/MulticallTests.swift | 31 +++++++++++++++++++ web3swift/src/Multicall/Multicall.swift | 24 ++++++-------- .../src/Multicall/MulticallContract.swift | 6 ++-- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/web3sTests/Multicall/MulticallTests.swift b/web3sTests/Multicall/MulticallTests.swift index 938a6d43..342b2774 100644 --- a/web3sTests/Multicall/MulticallTests.swift +++ b/web3sTests/Multicall/MulticallTests.swift @@ -47,6 +47,37 @@ class MulticallTests: XCTestCase { XCTAssertEqual(decimals, 18) XCTAssertEqual(name, "Uniswap") } + + func testNameAndSymbolMulticall2() async throws { + var aggregator = Multicall.Aggregator() + + var name: String? + var decimals: UInt8? + + try aggregator.append(ERC20Functions.decimals(contract: testContractAddress)) { output in + decimals = try ERC20Responses.decimalsResponse(data: output.get())?.value + } + + try aggregator.append( + function: ERC20Functions.name(contract: testContractAddress), + response: ERC20Responses.nameResponse.self + ) { result in + name = try? result.get() + } + + try aggregator.append(ERC20Functions.symbol(contract: testContractAddress)) + + do { + let response = try await multicall.tryAggregate(requireSuccess: true, calls: aggregator.calls) + let symbol = try ERC20Responses.symbolResponse(data: try response.outputs[2].get())?.value + XCTAssertEqual(symbol, "UNI") + } catch { + XCTFail("Unexpected failure while handling output") + } + + XCTAssertEqual(decimals, 18) + XCTAssertEqual(name, "Uniswap") + } } class MulticallWebSocketTests: MulticallTests { diff --git a/web3swift/src/Multicall/Multicall.swift b/web3swift/src/Multicall/Multicall.swift index 39e78afc..b308d9e2 100644 --- a/web3swift/src/Multicall/Multicall.swift +++ b/web3swift/src/Multicall/Multicall.swift @@ -37,10 +37,10 @@ public struct Multicall { throw MulticallError.executionFailed(error) } } - + public func tryAggregate(requireSuccess: Bool, calls: [Call]) async throws -> Multicall.Multicall2Response { let function = Contract.Functions.tryAggregate(contract: Contract.multicall2Address, requireSuccess: requireSuccess, calls: calls) - + do { let data = try await function.call(withClient: client, responseType: Multicall2Response.self) zip(calls, data.outputs) @@ -65,6 +65,7 @@ extension Multicall { } } } + public func tryAggregate(requireSuccess: Bool, calls: [Call], completionHandler: @escaping (Result) -> Void) { Task { do { @@ -109,35 +110,30 @@ extension Multicall { } } } - + public struct Multicall2Result: ABITuple { public static var types: [ABIType.Type] = [Bool.self, String.self] public var encodableValues: [ABIType] { [success, returnData] } - + public let success: Bool public let returnData: String - + public init?(values: [ABIDecoder.DecodedValue]) throws { self.success = try values[0].decoded() - self.returnData = try values[1].decoded() + self.returnData = try values[1].entry[0] } - + public func encode(to encoder: ABIFunctionEncoder) throws { try encoder.encode(success) try encoder.encode(returnData) } - - public init(success: Bool, returnData: String) { - self.success = success - self.returnData = returnData - } } - + public struct Multicall2Response: ABIResponse { static let multicallFailedError = "MULTICALL_FAIL".web3.keccak256.web3.hexString public static var types: [ABIType.Type] = [ABIArray.self] public let outputs: [Output] - + public init?(values: [ABIDecoder.DecodedValue]) throws { let results: [Multicall2Result] = try values[0].decodedTupleArray() self.outputs = results.map { result in diff --git a/web3swift/src/Multicall/MulticallContract.swift b/web3swift/src/Multicall/MulticallContract.swift index 89fdb4dc..7a06a94b 100644 --- a/web3swift/src/Multicall/MulticallContract.swift +++ b/web3swift/src/Multicall/MulticallContract.swift @@ -50,7 +50,7 @@ extension Multicall { try encoder.encode(calls) } } - + public struct tryAggregate: ABIFunction { public static let name = "tryAggregate" public let gasPrice: BigUInt? @@ -59,7 +59,7 @@ extension Multicall { public let from: EthereumAddress? public let requireSuccess: Bool public let calls: [Call] - + public init( contract: EthereumAddress, from: EthereumAddress? = nil, @@ -75,7 +75,7 @@ extension Multicall { self.requireSuccess = requireSuccess self.calls = calls } - + public func encode(to encoder: ABIFunctionEncoder) throws { try encoder.encode(requireSuccess) try encoder.encode(calls)