Skip to content

Commit

Permalink
[ADD] Support multicall v2 (#308)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tung Nguyen authored Feb 15, 2023
1 parent 040ad81 commit cf700b7
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
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)
}
}
}
}
}

0 comments on commit cf700b7

Please sign in to comment.