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 wildcard domain resolution (EIP-10) #214

Merged
merged 1 commit into from
May 24, 2022
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
15 changes: 15 additions & 0 deletions web3sTests/ENS/ENSOffchainTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,20 @@ class ENSOffchainTests: XCTestCase {
XCTFail("Expected ens but failed \(error).")
}
}

func testGivenRopstenRegistry_WhenWildcardSupported_AndAddressHasSubdomain_ThenResolvesCorrectly() async {
do {
let nameService = EthereumNameService(client: client!)

let address = try await nameService.resolve(
ens: "1.offchainexample.eth",
mode: .allowOffchainLookup
)

XCTAssertEqual(address, EthereumAddress("0x41563129cdbbd0c5d3e1c86cf9563926b243834d"))
} catch {
XCTFail("Expected ens but failed \(error).")
}
}
}

30 changes: 30 additions & 0 deletions web3sTests/ENS/ENSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,35 @@ class ENSTests: XCTestCase {
]
)
}

func testGivenRopstenRegistry_WhenWildcardSupported_AndAddressHasSubdomain_ThenResolvesExampleCorrectly() async {
do {
let nameService = EthereumNameService(client: client!)

let address = try await nameService.resolve(
ens: "ricmoose.hatch.eth",
mode: .onchain
)

XCTAssertEqual(address, EthereumAddress("0x4FaBE0A3a4DDd9968A7b4565184Ad0eFA7BE5411"))
} catch {
XCTFail("Expected ens but failed \(error).")
}
}

func testGivenRopstenRegistry_WhenWildcardNOTSupported_AndAddressHasSubdomain_ThenFailsResolving() async {
do {
let nameService = EthereumNameService(client: client!)

_ = try await nameService.resolve(
ens: "1.resolver.eth",
mode: .onchain
)

XCTFail("Expected error")
} catch {
XCTAssertEqual(error as? EthereumNameServiceError, .ensUnknown)
}
}
}

16 changes: 12 additions & 4 deletions web3swift/src/ENS/ENSResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ class ENSResolver {
let address: EthereumAddress
let callResolution: CallResolution
private (set) var supportsWildCard: Bool?
private let mustSupportWilcard: Bool

private let client: EthereumClientProtocol

init(
address: EthereumAddress,
client: EthereumClientProtocol,
callResolution: CallResolution,
supportsWildCard: Bool? = nil
supportsWildCard: Bool? = nil,
mustSupportWildcard: Bool = false
) {
self.address = address
self.callResolution = callResolution
self.client = client
self.supportsWildCard = supportsWildCard
self.mustSupportWilcard = mustSupportWildcard
}

func resolve(
Expand All @@ -39,7 +42,12 @@ class ENSResolver {
}
self.supportsWildCard = wildcardResolution

if wildcardResolution && callResolution.allowsOffchain {
if mustSupportWilcard && !wildcardResolution {
// Wildcard name resolution (ENSIP-10)
throw EthereumNameServiceError.ensUnknown
}

if wildcardResolution {
let response = try await ENSContracts.ENSOffchainResolverFunctions.resolve(
contract: address,
parameter: .name(name)
Expand Down Expand Up @@ -73,7 +81,7 @@ class ENSResolver {
}
self.supportsWildCard = wildcardResolution

if wildcardResolution && callResolution.allowsOffchain {
if wildcardResolution {
let response = try await ENSContracts.ENSOffchainResolverFunctions.resolve(
contract: self.address,
parameter: .address(address)
Expand Down Expand Up @@ -101,7 +109,7 @@ class ENSResolver {
}
}

private func supportsWildcard() async throws -> Bool {
func supportsWildcard() async throws -> Bool {
try await ERC165(client: client).supportsInterface(
contract: address,
id: ENSContracts.ENSOffchainResolverFunctions.interfaceId
Expand Down
177 changes: 110 additions & 67 deletions web3swift/src/ENS/EthereumNameService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ protocol EthereumNameServiceProtocol {

public enum EthereumNameServiceError: Error, Equatable {
case noNetwork
case noResolver
case ensUnknown
case invalidInput
case decodeIssue
Expand Down Expand Up @@ -87,39 +86,18 @@ public class EthereumNameService: EthereumNameServiceProtocol {
return completion(EthereumNameServiceError.noNetwork, nil)
}

let function = ENSContracts.ENSRegistryFunctions.resolver(
contract: registryAddress,
parameter: .address(address)
)
Task {
do {
let resolver = try await getResolver(
for: address,
registryAddress: registryAddress,
mode: mode
)

function.call(
withClient: client,
responseType: ENSContracts.AddressResponse.self,
block: .Latest,
resolution: .noOffchain(failOnExecutionError: true)
) { (error, response) in
if case .executionError = error {
return completion(.ensUnknown, nil)
}

guard let resolverAddress = response?.value else {
return completion(EthereumNameServiceError.noResolver, nil)
}

let resolver = self.resolversByAddress[resolverAddress] ?? ENSResolver(
address: resolverAddress,
client: self.client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
)
self.resolversByAddress[resolverAddress] = resolver

Task {
do {
let name = try await resolver.resolve(address: address)
completion(nil, name)
} catch let error {
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
}
let name = try await resolver.resolve(address: address)
completion(nil, name)
} catch let error {
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
}
}
}
Expand All @@ -132,41 +110,23 @@ public class EthereumNameService: EthereumNameServiceProtocol {
guard
let network = client.network,
let registryAddress = self.registryAddress ?? ENSContracts.registryAddress(for: network) else {
return completion(EthereumNameServiceError.noNetwork, nil)
}
let function = ENSContracts.ENSRegistryFunctions.resolver(
contract: registryAddress,
parameter: .name(ens)
)

function.call(
withClient: client,
responseType: ENSContracts.AddressResponse.self,
block: .Latest,
resolution: .noOffchain(failOnExecutionError: true)
) { (error, response) in
if case .executionError = error {
return completion(.ensUnknown, nil)
}

guard let resolverAddress = response?.value else {
return completion(EthereumNameServiceError.noResolver, nil)
}

let resolver = self.resolversByAddress[resolverAddress] ?? ENSResolver(
address: resolverAddress,
client: self.client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
)
self.resolversByAddress[resolverAddress] = resolver
return completion(EthereumNameServiceError.noNetwork, nil)
}
Task {
do {
let resolver = try await getResolver(
for: ens,
fullName: ens,
registryAddress: registryAddress,
mode: mode
)

Task {
do {
let address = try await resolver.resolve(name: ens)
completion(nil, address)
} catch let error {
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
}
let address = try await resolver.resolve(
name: ens
)
completion(nil, address)
} catch let error {
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
}
}
}
Expand Down Expand Up @@ -231,3 +191,86 @@ fileprivate extension ResolutionMode {
}
}
}


extension EthereumNameService {
private func getResolver(
for address: EthereumAddress,
registryAddress: EthereumAddress,
mode: ResolutionMode
) async throws -> ENSResolver {
let function = ENSContracts.ENSRegistryFunctions.resolver(
contract: registryAddress,
parameter: .address(address)
)

do {
let resolverAddress = try await function.call(
withClient: client,
responseType: ENSContracts.AddressResponse.self,
block: .Latest,
resolution: .noOffchain(failOnExecutionError: true)
).value

let resolver = self.resolversByAddress[resolverAddress] ?? ENSResolver(
address: resolverAddress,
client: client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
)
self.resolversByAddress[resolverAddress] = resolver
return resolver
} catch {
throw EthereumNameServiceError.ensUnknown
}
}

private func getResolver(
for name: String,
fullName: String,
registryAddress: EthereumAddress,
mode: ResolutionMode
) async throws -> ENSResolver {
let function = ENSContracts.ENSRegistryFunctions.resolver(
contract: registryAddress,
parameter: .name(name)
)

do {
let resolverAddress = try await function.call(
withClient: client,
responseType: ENSContracts.AddressResponse.self,
block: .Latest,
resolution: .noOffchain(failOnExecutionError: true)
).value

guard resolverAddress != .zero else {
// Wildcard name resolution (ENSIP-10)
let parent = name.split(separator: ".").dropFirst()

guard parent.count > 1 else {
throw EthereumNameServiceError.ensUnknown
}

let parentName = parent.joined(separator: ".")
return try await getResolver(
for: parentName,
fullName: fullName,
registryAddress: registryAddress,
mode: mode
)
}

let resolver = resolversByAddress[resolverAddress] ?? ENSResolver(
address: resolverAddress,
client: client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections),
mustSupportWildcard: fullName != name
)
self.resolversByAddress[resolverAddress] = resolver
return resolver
} catch {
throw error as? EthereumNameServiceError ?? .ensUnknown
}
}

}