Skip to content

Commit

Permalink
[ADD] Support wildcard domain resolution (EIP-10)
Browse files Browse the repository at this point in the history
  • Loading branch information
DarthMike committed May 23, 2022
1 parent 7ac7fe4 commit 255e8ac
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 66 deletions.
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
166 changes: 104 additions & 62 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 @@ -72,38 +71,18 @@ public class EthereumNameService: EthereumNameServiceProtocol {
return completion(EthereumNameServiceError.noNetwork, nil)
}

let function = ENSContracts.ENSRegistryFunctions.resolver(
contract: registryAddress,
parameter: .address(address)
)

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)
}

Task {
let resolver = ENSResolver(
address: resolverAddress,
client: self.client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
Task {
do {
let resolver = try await getResolver(
for: address,
registryAddress: registryAddress,
mode: mode
)

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 @@ -116,39 +95,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)
}
return completion(EthereumNameServiceError.noNetwork, nil)
}
Task {
do {
let resolver = try await getResolver(
for: ens,
fullName: ens,
registryAddress: registryAddress,
mode: mode
)

Task {
let resolver = ENSResolver(
address: resolverAddress,
client: self.client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
let address = try await resolver.resolve(
name: ens
)
do {
let address = try await resolver.resolve(name: ens)
completion(nil, address)
} catch let error {
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
}
completion(nil, address)
} catch let error {
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
}
}
}
Expand Down Expand Up @@ -213,3 +176,82 @@ 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

return ENSResolver(
address: resolverAddress,
client: client,
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
)
} 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
)
}

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

}

0 comments on commit 255e8ac

Please sign in to comment.