Skip to content

Commit fd1372e

Browse files
authored
Merge pull request #202 from argentlabs/tech/remove-0x-executionerror
[CHANGE] Remove workaround returning '0x' when eth_call fails
2 parents 41a4de3 + 02d7e06 commit fd1372e

File tree

9 files changed

+96
-69
lines changed

9 files changed

+96
-69
lines changed

web3sTests/Client/EthereumClientTests.swift

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ struct TransferMatchingSignatureEvent: ABIEvent {
3434

3535
class EthereumClientTests: XCTestCase {
3636
var client: EthereumClient?
37-
var mainnetClient: EthereumClient?
3837
var account: EthereumAccount?
3938
let timeout = 10.0
4039

4140
override func setUp() {
4241
super.setUp()
4342
self.client = EthereumClient(url: URL(string: TestConfig.clientUrl)!)
44-
self.mainnetClient = EthereumClient(url: URL(string: TestConfig.mainnetClientUrl)!)
4543
self.account = try? EthereumAccount(keyStorage: TestEthereumKeyStorage(privateKey: TestConfig.privateKey))
4644
print("Public address: \(self.account?.address.value ?? "NONE")")
4745
}
@@ -325,32 +323,20 @@ extension EthereumClientTests {
325323
}
326324
}
327325

328-
// This is how geth used to work up until a recent version
329-
// see https://github.com/ethereum/go-ethereum/pull/21083/
330-
// Used to return '0x' in response, and would fail decoding
331-
// We'll continue to support this as user of library (and Argent in our case)
332-
// works with this assumption.
333-
// NOTE: This behaviour will be removed at a later time to fail as expected
334-
// NOTE: At the time of writing, this test succeeds as-is in ropsten as nodes behaviour is different. That's why we use a mainnet check here
335-
func test_GivenUnimplementedMethod_WhenCallingContract_ThenFailsWith0x_Async() async {
326+
func test_GivenUnimplementedMethod_WhenCallingContract_ThenFailsWithExecutionError_Async() async {
336327
do {
337328
let function = InvalidMethodA(param: .zero)
338-
let _ = try await function.call(withClient: self.mainnetClient!,
339-
responseType: InvalidMethodA.BoolResponse.self)
329+
let _ = try await function.call(
330+
withClient: self.client!,
331+
responseType: InvalidMethodA.BoolResponse.self)
340332
XCTFail("Expected to throw while awaiting, but succeeded")
341333
} catch {
342-
XCTAssertEqual(error as? EthereumClientError, .decodeIssue)
343-
}
344-
}
345-
346-
func test_GivenFailingCallMethod_WhenCallingContract_ThenFailsWith0x_Async() async {
347-
do {
348-
let function = InvalidMethodB(param: .zero)
349-
let _ = try await function.call(withClient: self.mainnetClient!,
350-
responseType: InvalidMethodB.BoolResponse.self)
351-
XCTFail("Expected to throw while awaiting, but succeeded")
352-
} catch {
353-
XCTAssertEqual(error as? EthereumClientError, .decodeIssue)
334+
XCTAssertEqual(
335+
error as? EthereumClientError,
336+
.executionError(
337+
.init(code: -32000, message: "execution reverted")
338+
)
339+
)
354340
}
355341
}
356342

web3sTests/ERC20/ERC20Tests.swift

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,6 @@ class ERC20Tests: XCTestCase {
4545
waitForExpectations(timeout: 10)
4646
}
4747

48-
func testZeroDecimals() {
49-
let expect = expectation(description: "Get token decimals (0)")
50-
erc20?.decimals(tokenContract: EthereumAddress("0x40dd3ac2481960cf34d96e647dd0bc52a1f03f52"), completion: { (error, decimals) in
51-
XCTAssertNil(error)
52-
XCTAssertEqual(decimals, 0)
53-
expect.fulfill()
54-
})
55-
waitForExpectations(timeout: 10)
56-
}
57-
5848
func testSymbol() {
5949
let expect = expectation(description: "Get token symbol")
6050
erc20?.symbol(tokenContract: self.testContractAddress, completion: { (error, symbol) in
@@ -151,7 +141,7 @@ extension ERC20Tests {
151141
}
152142
}
153143

154-
func testZeroDecimals_Async() async {
144+
func testNoDecimals_Async() async {
155145
do {
156146
let decimals = try await erc20?.decimals(tokenContract: EthereumAddress("0x40dd3ac2481960cf34d96e647dd0bc52a1f03f52"))
157147
XCTAssertEqual(decimals, 0)

web3sTests/TestConfig.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ struct TestConfig {
1212
// This is the proxy URL for connecting to the Blockchain. For testing we usually use the Ropsten network on Infura. Using free tier, so might hit rate limits
1313
static let clientUrl = "https://ropsten.infura.io/v3/b2f4b3f635d8425c96854c3d28ba6bb0"
1414

15-
// Same for mainnet
16-
static let mainnetClientUrl = "https://mainnet.infura.io/v3/b2f4b3f635d8425c96854c3d28ba6bb0"
17-
1815
// An EOA with some Ether, so that we can test sending transactions (pay for gas)
1916
static let privateKey = "0xef4e182ae2cf32192d2a62c1159c8c4f7f2d658c303d0dfca5791a205456a132"
2017

web3swift/src/Client/EthereumClient.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public protocol EthereumClientProtocol {
8080

8181
public enum EthereumClientError: Error, Equatable {
8282
case tooManyResults
83-
case executionError(description: String)
83+
case executionError(JSONRPCErrorDetail)
8484
case unexpectedReturnValue
8585
case noResultFound
8686
case decodeIssue
@@ -266,10 +266,10 @@ public class EthereumClient: EthereumClientProtocol {
266266
EthereumRPC.execute(session: session, url: url, method: "eth_estimateGas", params: params, receive: String.self) { (error, response) in
267267
if let gasHex = response as? String, let gas = BigUInt(hex: gasHex) {
268268
completion(nil, gas)
269-
} else if case let .executionError(errorResult) = error as? JSONRPCError {
270-
completion(EthereumClientError.executionError(description: "\(errorResult.error)"), nil)
269+
} else if case let .executionError(result) = error as? JSONRPCError {
270+
completion(.executionError(result.error), nil)
271271
} else {
272-
completion(EthereumClientError.unexpectedReturnValue, nil)
272+
completion(.unexpectedReturnValue, nil)
273273
}
274274
}
275275
}
@@ -380,13 +380,10 @@ public class EthereumClient: EthereumClientProtocol {
380380
EthereumRPC.execute(session: session, url: url, method: "eth_call", params: params, receive: String.self) { (error, response) in
381381
if let resDataString = response as? String {
382382
completion(nil, resDataString)
383-
} else if
384-
let error = error,
385-
case let JSONRPCError.executionError(result) = error,
386-
(result.error.code == JSONRPCErrorCode.invalidInput || result.error.code == JSONRPCErrorCode.contractExecution) {
387-
completion(nil, "0x")
383+
} else if case let .executionError(result) = error as? JSONRPCError {
384+
completion(.executionError(result.error), nil)
388385
} else {
389-
completion(EthereumClientError.unexpectedReturnValue, nil)
386+
completion(.unexpectedReturnValue, nil)
390387
}
391388
}
392389
}

web3swift/src/Client/JSONRPC.swift

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,32 @@ struct JSONRPCRequest<T: Encodable>: Encodable {
2020
}
2121

2222
public struct JSONRPCResult<T: Decodable>: Decodable {
23-
var id: Int
24-
var jsonrpc: String
25-
var result: T
23+
public var id: Int
24+
public var jsonrpc: String
25+
public var result: T
2626
}
2727

28-
public struct JSONRPCErrorDetail: Decodable {
29-
var code: Int
30-
var message: String
28+
public struct JSONRPCErrorDetail: Decodable, Equatable, CustomStringConvertible {
29+
public var code: Int
30+
public var message: String
31+
32+
public init(
33+
code: Int,
34+
message: String
35+
) {
36+
self.code = code
37+
self.message = message
38+
}
39+
40+
public var description: String {
41+
"Code: \(code)\nMessage: \(message)"
42+
}
3143
}
3244

3345
public struct JSONRPCErrorResult: Decodable {
34-
var id: Int
35-
var jsonrpc: String
36-
var error: JSONRPCErrorDetail
46+
public var id: Int
47+
public var jsonrpc: String
48+
public var error: JSONRPCErrorDetail
3749
}
3850

3951
public enum JSONRPCErrorCode {
@@ -50,7 +62,7 @@ public enum JSONRPCError: Error {
5062
case unknownError
5163
case noResult
5264

53-
var isExecutionError: Bool {
65+
public var isExecutionError: Bool {
5466
switch self {
5567
case .executionError:
5668
return true

web3swift/src/Contract/Statically Typed/EthereumClient+Static.swift

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,41 @@ public extension ABIFunction {
2626

2727
}
2828

29-
func call<T: ABIResponse>(withClient client: EthereumClientProtocol, responseType: T.Type, block: EthereumBlock = .Latest, completion: @escaping((EthereumClientError?, T?) -> Void)) {
29+
func call<T: ABIResponse>(
30+
withClient client: EthereumClientProtocol,
31+
responseType: T.Type,
32+
block: EthereumBlock = .Latest,
33+
failOnExecutionError: Bool = true,
34+
completion: @escaping((EthereumClientError?, T?) -> Void)
35+
) {
3036

3137
guard let tx = try? self.transaction() else {
3238
return completion(EthereumClientError.encodeIssue, nil)
3339
}
3440

3541
client.eth_call(tx, block: block) { (error, res) in
36-
guard let res = res, error == nil else {
37-
return completion(EthereumClientError.unexpectedReturnValue, nil)
38-
}
42+
let parseOrFail: (String) -> Void = { data in
43+
guard let response = (try? T(data: data)) else {
44+
return completion(EthereumClientError.decodeIssue, nil)
45+
}
3946

40-
guard let response = (try? T(data: res)) else {
41-
return completion(EthereumClientError.decodeIssue, nil)
47+
return completion(nil, response)
4248
}
4349

44-
return completion(nil, response)
50+
switch (error, res) {
51+
case (.executionError, _):
52+
if failOnExecutionError {
53+
return completion(error, nil)
54+
} else {
55+
return parseOrFail("0x")
56+
}
57+
case (let error?, _):
58+
return completion(error, nil)
59+
case (nil, let data?):
60+
parseOrFail(data)
61+
case (nil, nil):
62+
return completion(EthereumClientError.unexpectedReturnValue, nil)
63+
}
4564
}
4665
}
4766
}
@@ -62,9 +81,19 @@ public extension ABIFunction {
6281
}
6382
}
6483

65-
func call<T: ABIResponse>(withClient client: EthereumClientProtocol, responseType: T.Type, block: EthereumBlock = .Latest) async throws -> T {
84+
func call<T: ABIResponse>(
85+
withClient client: EthereumClientProtocol,
86+
responseType: T.Type,
87+
block: EthereumBlock = .Latest,
88+
failOnExecutionError: Bool = true
89+
) async throws -> T {
6690
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<T, Error>) in
67-
call(withClient: client, responseType: responseType) { error, response in
91+
call(
92+
withClient: client,
93+
responseType: responseType,
94+
block: block,
95+
failOnExecutionError: failOnExecutionError
96+
) { error, response in
6897
if let error = error {
6998
continuation.resume(throwing: error)
7099
} else if let response = response {

web3swift/src/ENS/EthereumNameService.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public class EthereumNameService: EthereumNameServiceProtocol {
5858
}
5959

6060
client.eth_call(registryTransaction, block: .Latest, completion: { (error, resolverData) in
61+
if case .executionError = error {
62+
return completion(.ensUnknown, nil)
63+
}
64+
6165
guard let resolverData = resolverData else {
6266
return completion(EthereumNameServiceError.noResolver, nil)
6367
}
@@ -107,6 +111,10 @@ public class EthereumNameService: EthereumNameServiceProtocol {
107111
}
108112

109113
client.eth_call(registryTransaction, block: .Latest, completion: { (error, resolverData) in
114+
if case .executionError = error {
115+
return completion(.ensUnknown, nil)
116+
}
117+
110118
guard let resolverData = resolverData else {
111119
return completion(EthereumNameServiceError.noResolver, nil)
112120
}

web3swift/src/ERC20/ERC20.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ public class ERC20: ERC20Protocol {
6666

6767
public func decimals(tokenContract: EthereumAddress, completion: @escaping((Error?, UInt8?) -> Void)) {
6868
let function = ERC20Functions.decimals(contract: tokenContract)
69-
function.call(withClient: self.client, responseType: ERC20Responses.decimalsResponse.self) { (error, decimalsResponse) in
69+
function.call(
70+
withClient: self.client,
71+
responseType: ERC20Responses.decimalsResponse.self,
72+
failOnExecutionError: false
73+
) { (error, decimalsResponse) in
7074
return completion(error, decimalsResponse?.value)
7175
}
7276
}

web3swift/src/ERC721/ERC721.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,11 @@ public class ERC721Enumerable: ERC721 {
347347
index: BigUInt,
348348
completion: @escaping((Error?, BigUInt?) -> Void)) {
349349
let function = ERC721EnumerableFunctions.tokenOfOwnerByIndex(contract: contract, address: owner, index: index)
350-
function.call(withClient: client, responseType: ERC721EnumerableResponses.numberResponse.self) { error, response in
350+
function.call(
351+
withClient: client,
352+
responseType: ERC721EnumerableResponses.numberResponse.self,
353+
failOnExecutionError: false
354+
) { error, response in
351355
return completion(error, response?.value)
352356
}
353357
}

0 commit comments

Comments
 (0)