Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] Support multicall v2 #308

Merged
merged 2 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions web3sTests/Multicall/MulticallTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
60 changes: 60 additions & 0 deletions web3swift/src/Multicall/Multicall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -50,6 +65,17 @@ extension Multicall {
}
}
}

public func tryAggregate(requireSuccess: Bool, calls: [Call], completionHandler: @escaping (Result<Multicall2Response, MulticallError>) -> 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 {
Expand Down Expand Up @@ -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<Multicall2Result>.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] }
Expand Down
32 changes: 32 additions & 0 deletions web3swift/src/Multicall/MulticallContract.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
}
}
}
}