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 4b62546e..b308d9e2 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,17 @@ 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 { @@ -85,6 +111,40 @@ 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].entry[0] + } + + public func encode(to encoder: ABIFunctionEncoder) throws { + try encoder.encode(success) + try encoder.encode(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] public var encodableValues: [ABIType] { [target, encodedFunction] } diff --git a/web3swift/src/Multicall/MulticallContract.swift b/web3swift/src/Multicall/MulticallContract.swift index b740cf0e..7a06a94b 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) + } + } } } }