Skip to content

Commit 46f5950

Browse files
authored
Merge pull request #214 from argentlabs/feature/ens-wildcard-resolution
[ADD] Support wildcard domain resolution (EIP-10)
2 parents 8ba8810 + c19159b commit 46f5950

File tree

4 files changed

+167
-71
lines changed

4 files changed

+167
-71
lines changed

web3sTests/ENS/ENSOffchainTests.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,20 @@ class ENSOffchainTests: XCTestCase {
6868
XCTFail("Expected ens but failed \(error).")
6969
}
7070
}
71+
72+
func testGivenRopstenRegistry_WhenWildcardSupported_AndAddressHasSubdomain_ThenResolvesCorrectly() async {
73+
do {
74+
let nameService = EthereumNameService(client: client!)
75+
76+
let address = try await nameService.resolve(
77+
ens: "1.offchainexample.eth",
78+
mode: .allowOffchainLookup
79+
)
80+
81+
XCTAssertEqual(address, EthereumAddress("0x41563129cdbbd0c5d3e1c86cf9563926b243834d"))
82+
} catch {
83+
XCTFail("Expected ens but failed \(error).")
84+
}
85+
}
7186
}
7287

web3sTests/ENS/ENSTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,5 +176,35 @@ class ENSTests: XCTestCase {
176176
]
177177
)
178178
}
179+
180+
func testGivenRopstenRegistry_WhenWildcardSupported_AndAddressHasSubdomain_ThenResolvesExampleCorrectly() async {
181+
do {
182+
let nameService = EthereumNameService(client: client!)
183+
184+
let address = try await nameService.resolve(
185+
ens: "ricmoose.hatch.eth",
186+
mode: .onchain
187+
)
188+
189+
XCTAssertEqual(address, EthereumAddress("0x4FaBE0A3a4DDd9968A7b4565184Ad0eFA7BE5411"))
190+
} catch {
191+
XCTFail("Expected ens but failed \(error).")
192+
}
193+
}
194+
195+
func testGivenRopstenRegistry_WhenWildcardNOTSupported_AndAddressHasSubdomain_ThenFailsResolving() async {
196+
do {
197+
let nameService = EthereumNameService(client: client!)
198+
199+
_ = try await nameService.resolve(
200+
ens: "1.resolver.eth",
201+
mode: .onchain
202+
)
203+
204+
XCTFail("Expected error")
205+
} catch {
206+
XCTAssertEqual(error as? EthereumNameServiceError, .ensUnknown)
207+
}
208+
}
179209
}
180210

web3swift/src/ENS/ENSResolver.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,22 @@ class ENSResolver {
1313
let address: EthereumAddress
1414
let callResolution: CallResolution
1515
private (set) var supportsWildCard: Bool?
16+
private let mustSupportWilcard: Bool
1617

1718
private let client: EthereumClientProtocol
1819

1920
init(
2021
address: EthereumAddress,
2122
client: EthereumClientProtocol,
2223
callResolution: CallResolution,
23-
supportsWildCard: Bool? = nil
24+
supportsWildCard: Bool? = nil,
25+
mustSupportWildcard: Bool = false
2426
) {
2527
self.address = address
2628
self.callResolution = callResolution
2729
self.client = client
2830
self.supportsWildCard = supportsWildCard
31+
self.mustSupportWilcard = mustSupportWildcard
2932
}
3033

3134
func resolve(
@@ -39,7 +42,12 @@ class ENSResolver {
3942
}
4043
self.supportsWildCard = wildcardResolution
4144

42-
if wildcardResolution && callResolution.allowsOffchain {
45+
if mustSupportWilcard && !wildcardResolution {
46+
// Wildcard name resolution (ENSIP-10)
47+
throw EthereumNameServiceError.ensUnknown
48+
}
49+
50+
if wildcardResolution {
4351
let response = try await ENSContracts.ENSOffchainResolverFunctions.resolve(
4452
contract: address,
4553
parameter: .name(name)
@@ -73,7 +81,7 @@ class ENSResolver {
7381
}
7482
self.supportsWildCard = wildcardResolution
7583

76-
if wildcardResolution && callResolution.allowsOffchain {
84+
if wildcardResolution {
7785
let response = try await ENSContracts.ENSOffchainResolverFunctions.resolve(
7886
contract: self.address,
7987
parameter: .address(address)
@@ -101,7 +109,7 @@ class ENSResolver {
101109
}
102110
}
103111

104-
private func supportsWildcard() async throws -> Bool {
112+
func supportsWildcard() async throws -> Bool {
105113
try await ERC165(client: client).supportsInterface(
106114
contract: address,
107115
id: ENSContracts.ENSOffchainResolverFunctions.interfaceId

web3swift/src/ENS/EthereumNameService.swift

Lines changed: 110 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ protocol EthereumNameServiceProtocol {
3939

4040
public enum EthereumNameServiceError: Error, Equatable {
4141
case noNetwork
42-
case noResolver
4342
case ensUnknown
4443
case invalidInput
4544
case decodeIssue
@@ -87,39 +86,18 @@ public class EthereumNameService: EthereumNameServiceProtocol {
8786
return completion(EthereumNameServiceError.noNetwork, nil)
8887
}
8988

90-
let function = ENSContracts.ENSRegistryFunctions.resolver(
91-
contract: registryAddress,
92-
parameter: .address(address)
93-
)
89+
Task {
90+
do {
91+
let resolver = try await getResolver(
92+
for: address,
93+
registryAddress: registryAddress,
94+
mode: mode
95+
)
9496

95-
function.call(
96-
withClient: client,
97-
responseType: ENSContracts.AddressResponse.self,
98-
block: .Latest,
99-
resolution: .noOffchain(failOnExecutionError: true)
100-
) { (error, response) in
101-
if case .executionError = error {
102-
return completion(.ensUnknown, nil)
103-
}
104-
105-
guard let resolverAddress = response?.value else {
106-
return completion(EthereumNameServiceError.noResolver, nil)
107-
}
108-
109-
let resolver = self.resolversByAddress[resolverAddress] ?? ENSResolver(
110-
address: resolverAddress,
111-
client: self.client,
112-
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
113-
)
114-
self.resolversByAddress[resolverAddress] = resolver
115-
116-
Task {
117-
do {
118-
let name = try await resolver.resolve(address: address)
119-
completion(nil, name)
120-
} catch let error {
121-
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
122-
}
97+
let name = try await resolver.resolve(address: address)
98+
completion(nil, name)
99+
} catch let error {
100+
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
123101
}
124102
}
125103
}
@@ -132,41 +110,23 @@ public class EthereumNameService: EthereumNameServiceProtocol {
132110
guard
133111
let network = client.network,
134112
let registryAddress = self.registryAddress ?? ENSContracts.registryAddress(for: network) else {
135-
return completion(EthereumNameServiceError.noNetwork, nil)
136-
}
137-
let function = ENSContracts.ENSRegistryFunctions.resolver(
138-
contract: registryAddress,
139-
parameter: .name(ens)
140-
)
141-
142-
function.call(
143-
withClient: client,
144-
responseType: ENSContracts.AddressResponse.self,
145-
block: .Latest,
146-
resolution: .noOffchain(failOnExecutionError: true)
147-
) { (error, response) in
148-
if case .executionError = error {
149-
return completion(.ensUnknown, nil)
150-
}
151-
152-
guard let resolverAddress = response?.value else {
153-
return completion(EthereumNameServiceError.noResolver, nil)
154-
}
155-
156-
let resolver = self.resolversByAddress[resolverAddress] ?? ENSResolver(
157-
address: resolverAddress,
158-
client: self.client,
159-
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
160-
)
161-
self.resolversByAddress[resolverAddress] = resolver
113+
return completion(EthereumNameServiceError.noNetwork, nil)
114+
}
115+
Task {
116+
do {
117+
let resolver = try await getResolver(
118+
for: ens,
119+
fullName: ens,
120+
registryAddress: registryAddress,
121+
mode: mode
122+
)
162123

163-
Task {
164-
do {
165-
let address = try await resolver.resolve(name: ens)
166-
completion(nil, address)
167-
} catch let error {
168-
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
169-
}
124+
let address = try await resolver.resolve(
125+
name: ens
126+
)
127+
completion(nil, address)
128+
} catch let error {
129+
completion(error as? EthereumNameServiceError ?? .ensUnknown, nil)
170130
}
171131
}
172132
}
@@ -231,3 +191,86 @@ fileprivate extension ResolutionMode {
231191
}
232192
}
233193
}
194+
195+
196+
extension EthereumNameService {
197+
private func getResolver(
198+
for address: EthereumAddress,
199+
registryAddress: EthereumAddress,
200+
mode: ResolutionMode
201+
) async throws -> ENSResolver {
202+
let function = ENSContracts.ENSRegistryFunctions.resolver(
203+
contract: registryAddress,
204+
parameter: .address(address)
205+
)
206+
207+
do {
208+
let resolverAddress = try await function.call(
209+
withClient: client,
210+
responseType: ENSContracts.AddressResponse.self,
211+
block: .Latest,
212+
resolution: .noOffchain(failOnExecutionError: true)
213+
).value
214+
215+
let resolver = self.resolversByAddress[resolverAddress] ?? ENSResolver(
216+
address: resolverAddress,
217+
client: client,
218+
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections)
219+
)
220+
self.resolversByAddress[resolverAddress] = resolver
221+
return resolver
222+
} catch {
223+
throw EthereumNameServiceError.ensUnknown
224+
}
225+
}
226+
227+
private func getResolver(
228+
for name: String,
229+
fullName: String,
230+
registryAddress: EthereumAddress,
231+
mode: ResolutionMode
232+
) async throws -> ENSResolver {
233+
let function = ENSContracts.ENSRegistryFunctions.resolver(
234+
contract: registryAddress,
235+
parameter: .name(name)
236+
)
237+
238+
do {
239+
let resolverAddress = try await function.call(
240+
withClient: client,
241+
responseType: ENSContracts.AddressResponse.self,
242+
block: .Latest,
243+
resolution: .noOffchain(failOnExecutionError: true)
244+
).value
245+
246+
guard resolverAddress != .zero else {
247+
// Wildcard name resolution (ENSIP-10)
248+
let parent = name.split(separator: ".").dropFirst()
249+
250+
guard parent.count > 1 else {
251+
throw EthereumNameServiceError.ensUnknown
252+
}
253+
254+
let parentName = parent.joined(separator: ".")
255+
return try await getResolver(
256+
for: parentName,
257+
fullName: fullName,
258+
registryAddress: registryAddress,
259+
mode: mode
260+
)
261+
}
262+
263+
let resolver = resolversByAddress[resolverAddress] ?? ENSResolver(
264+
address: resolverAddress,
265+
client: client,
266+
callResolution: mode.callResolution(maxRedirects: self.maximumRedirections),
267+
mustSupportWildcard: fullName != name
268+
)
269+
self.resolversByAddress[resolverAddress] = resolver
270+
return resolver
271+
} catch {
272+
throw error as? EthereumNameServiceError ?? .ensUnknown
273+
}
274+
}
275+
276+
}

0 commit comments

Comments
 (0)