From eea9749228579393542ca4bd848198b2ebd9858d Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Mon, 21 Nov 2022 07:39:06 -0800 Subject: [PATCH 01/30] fix bugs --- Sources/Core/EthereumABI/ABIElements.swift | 4 +-- .../web3swift/Operations/WriteOperation.swift | 33 +++++++++++-------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index ac111458b..bb269b9c5 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -202,7 +202,7 @@ extension ABI.Element.Function { /// Encode parameters of a given contract method /// - Parameter parameters: Parameters to pass to Ethereum contract - /// - Returns: Encoded data + /// - Returns: Encoded data public func encodeParameters(_ parameters: [AnyObject]) -> Data? { guard parameters.count == inputs.count, let data = ABIEncoder.encode(types: inputs, values: parameters) else { return nil } @@ -325,7 +325,7 @@ extension ABI.Element.Function { // set a flag to detect the request succeeded } - if returnArray.isEmpty { + if returnArray.isEmpty && !outputs.isEmpty { return nil } diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index 9c84202e7..8d27bb533 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -15,21 +15,26 @@ public class WriteOperation: ReadOperation { /// - Parameters: /// - password: Password for private key. /// - policies: Custom policies for how to resolve (optional). Default is auto. - public func writeToChain(password: String, policies: Policies = .auto) async throws -> TransactionSendingResult { + public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = false) async throws -> TransactionSendingResult { try await policyResolver.resolveAll(for: &transaction, with: policies) - if let attachedKeystoreManager = self.web3.provider.attachedKeystoreManager { - do { - try Web3Signer.signTX(transaction: &transaction, - keystore: attachedKeystoreManager, - account: transaction.from ?? transaction.sender ?? EthereumAddress.contractDeploymentAddress(), - password: password) - } catch { - throw Web3Error.inputError(desc: "Failed to locally sign a transaction") - } - guard let transactionData = transaction.encode(for: .transaction) else { throw Web3Error.dataError } - return try await web3.eth.send(raw: transactionData) + + guard sendRaw else { + return try await web3.eth.send(transaction) + } + + guard let attachedKeystoreManager = web3.provider.attachedKeystoreManager else { + throw Web3Error.inputError(desc: "Failed to locally sign a transaction. Web3 provider doesn't have keystore attached.") + } + + do { + try Web3Signer.signTX(transaction: &transaction, + keystore: attachedKeystoreManager, + account: transaction.from ?? transaction.sender ?? EthereumAddress.contractDeploymentAddress(), + password: password) + } catch { + throw Web3Error.inputError(desc: "Failed to locally sign a transaction. \(error.localizedDescription)") } - // MARK: Sending Data flow - return try await web3.eth.send(transaction) + guard let transactionData = transaction.encode(for: .transaction) else { throw Web3Error.dataError } + return try await web3.eth.send(raw: transactionData) } } From 34e312a9ad8afd66966b0f08ae263ee16b6e548d Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:18:02 +0200 Subject: [PATCH 02/30] fix: documentation update --- Sources/web3swift/Operations/WriteOperation.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index 8d27bb533..80b9780ef 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -13,8 +13,13 @@ public class WriteOperation: ReadOperation { // FIXME: Rewrite this to CodableTransaction /// Sends raw transaction for write operation. /// - Parameters: - /// - password: Password for private key. - /// - policies: Custom policies for how to resolve (optional). Default is auto. + /// - password: Password for the private key in the keystore manager attached to the provider + /// you set to `web3` passed in the initializer. + /// - policies: Determining the behaviour of how transaction attributes like gas limit and + /// nonce are resolved. Default value is ``Policies/auto``. + /// - sendRaw: If set to `true` transaction will be signed and sent using `eth_sendRawTransaction`. + /// Otherwise, no signing attempts will take place and the `eth_sendTransaction` RPC will be used instead. + /// Default value is `false`. public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = false) async throws -> TransactionSendingResult { try await policyResolver.resolveAll(for: &transaction, with: policies) From 4207de82b5a468b010e812edbbac38279f8992e5 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Mon, 21 Nov 2022 23:21:05 +0200 Subject: [PATCH 03/30] fix: documentation update for func writeToChain --- Sources/web3swift/Operations/WriteOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index 80b9780ef..9e2cca401 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -11,7 +11,7 @@ import Core public class WriteOperation: ReadOperation { // FIXME: Rewrite this to CodableTransaction - /// Sends raw transaction for write operation. + /// Sends signed or unsigned transaction for write operation. /// - Parameters: /// - password: Password for the private key in the keystore manager attached to the provider /// you set to `web3` passed in the initializer. From ff055020cfd616d1a9c8d4a3d10b4c35ccf7da2e Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Mon, 21 Nov 2022 18:14:07 -0800 Subject: [PATCH 04/30] update per feedback --- Sources/web3swift/Operations/WriteOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index 9e2cca401..c380c64f9 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -20,7 +20,7 @@ public class WriteOperation: ReadOperation { /// - sendRaw: If set to `true` transaction will be signed and sent using `eth_sendRawTransaction`. /// Otherwise, no signing attempts will take place and the `eth_sendTransaction` RPC will be used instead. /// Default value is `false`. - public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = false) async throws -> TransactionSendingResult { + public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = true) async throws -> TransactionSendingResult { try await policyResolver.resolveAll(for: &transaction, with: policies) guard sendRaw else { From 6b69769439988460eddb9b256fc3560da2a37d9e Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Tue, 22 Nov 2022 07:35:54 -0800 Subject: [PATCH 05/30] attempt test fix --- Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift index f31eaf1ac..3d36e713f 100755 --- a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift +++ b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift @@ -132,7 +132,7 @@ class AdvancedABIv2Tests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) From 713c63d3b37c7066ff172bc84507bdea571289d2 Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Tue, 22 Nov 2022 08:08:00 -0800 Subject: [PATCH 06/30] working on tests --- Sources/web3swift/Operations/WriteOperation.swift | 2 +- Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index c380c64f9..9e2cca401 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -20,7 +20,7 @@ public class WriteOperation: ReadOperation { /// - sendRaw: If set to `true` transaction will be signed and sent using `eth_sendRawTransaction`. /// Otherwise, no signing attempts will take place and the `eth_sendTransaction` RPC will be used instead. /// Default value is `false`. - public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = true) async throws -> TransactionSendingResult { + public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = false) async throws -> TransactionSendingResult { try await policyResolver.resolveAll(for: &transaction, with: policies) guard sendRaw else { diff --git a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift index 3d36e713f..f31eaf1ac 100755 --- a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift +++ b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift @@ -132,7 +132,7 @@ class AdvancedABIv2Tests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) From aaaabe7814210f442657eb7e57645d30491941bd Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:08:18 -0800 Subject: [PATCH 07/30] restore true --- Sources/web3swift/Operations/WriteOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index 9e2cca401..c380c64f9 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -20,7 +20,7 @@ public class WriteOperation: ReadOperation { /// - sendRaw: If set to `true` transaction will be signed and sent using `eth_sendRawTransaction`. /// Otherwise, no signing attempts will take place and the `eth_sendTransaction` RPC will be used instead. /// Default value is `false`. - public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = false) async throws -> TransactionSendingResult { + public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = true) async throws -> TransactionSendingResult { try await policyResolver.resolveAll(for: &transaction, with: policies) guard sendRaw else { From 9fc15082b82c4e4374447c4a2c99bd1fa7d07d43 Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:25:51 -0800 Subject: [PATCH 08/30] amend tests --- .../web3swiftTests/localTests/AdvancedABIv2Tests.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift index f31eaf1ac..e34aed0f8 100755 --- a/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift +++ b/Tests/web3swiftTests/localTests/AdvancedABIv2Tests.swift @@ -25,7 +25,7 @@ class AdvancedABIv2Tests: LocalTestCase { deployTx.transaction.from = allAddresses[0] // MARK: Sending Data flow let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) @@ -61,7 +61,7 @@ class AdvancedABIv2Tests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) @@ -97,7 +97,7 @@ class AdvancedABIv2Tests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) @@ -132,7 +132,7 @@ class AdvancedABIv2Tests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) @@ -168,7 +168,7 @@ class AdvancedABIv2Tests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() Thread.sleep(forTimeInterval: 1.0) From 13bac57aad9d2a7a58253f66509025674e391f2b Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:34:25 -0800 Subject: [PATCH 09/30] adjust helpers --- Tests/web3swiftTests/localTests/LocalTestCase.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/LocalTestCase.swift b/Tests/web3swiftTests/localTests/LocalTestCase.swift index 720d01f35..c44dab412 100644 --- a/Tests/web3swiftTests/localTests/LocalTestCase.swift +++ b/Tests/web3swiftTests/localTests/LocalTestCase.swift @@ -30,7 +30,7 @@ class LocalTestCase: XCTestCase { writeTX.transaction.value = value let policies = Policies(gasLimitPolicy: .manual(78423), gasPricePolicy: .manual(20000000000)) for _ in block..<25 { - _ = try! await writeTX.writeToChain(password: "", policies: policies) + _ = try! await writeTX.writeToChain(password: "", policies: policies, sendRaw: false) } } } From 2213fd0ad8b1ac5c24a4f03b69f6a8525fd16306 Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Thu, 24 Nov 2022 10:30:46 -0800 Subject: [PATCH 10/30] adjust test for deploy changes --- Tests/web3swiftTests/localTests/BasicLocalNodeTests.swift | 4 ++-- Tests/web3swiftTests/localTests/ERC20ClassTests.swift | 2 +- Tests/web3swiftTests/localTests/PersonalSignatureTests.swift | 2 +- Tests/web3swiftTests/localTests/TestHelpers.swift | 2 +- Tests/web3swiftTests/localTests/UserCases.swift | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/web3swiftTests/localTests/BasicLocalNodeTests.swift b/Tests/web3swiftTests/localTests/BasicLocalNodeTests.swift index bddfe6a1e..c099f330d 100755 --- a/Tests/web3swiftTests/localTests/BasicLocalNodeTests.swift +++ b/Tests/web3swiftTests/localTests/BasicLocalNodeTests.swift @@ -25,7 +25,7 @@ class BasicLocalNodeTests: LocalTestCase { let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = result.hash.stripHexPrefix() while true { @@ -60,7 +60,7 @@ class BasicLocalNodeTests: LocalTestCase { print("Balance before to: " + balanceBeforeTo.description) print("Balance before from: " + balanceBeforeFrom.description) - let result = try await sendTx.writeToChain(password: "web3swift") + let result = try await sendTx.writeToChain(password: "web3swift", sendRaw: false) let txHash = Data.fromHex(result.hash.stripHexPrefix())! Thread.sleep(forTimeInterval: 1.0) diff --git a/Tests/web3swiftTests/localTests/ERC20ClassTests.swift b/Tests/web3swiftTests/localTests/ERC20ClassTests.swift index 6ae53cf5a..2bda587d8 100755 --- a/Tests/web3swiftTests/localTests/ERC20ClassTests.swift +++ b/Tests/web3swiftTests/localTests/ERC20ClassTests.swift @@ -39,7 +39,7 @@ class ERC20ClassTests: LocalTestCase { func testERC20tokenBalanceAndAllowance() async throws { let (web3, _, receipt, _) = try await TestHelpers.localDeployERC20() - let erc20token = ERC20.init(web3: web3, provider: web3.provider, address: receipt.contractAddress!) + let erc20token = ERC20(web3: web3, provider: web3.provider, address: receipt.contractAddress!) let userAddress = EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")! diff --git a/Tests/web3swiftTests/localTests/PersonalSignatureTests.swift b/Tests/web3swiftTests/localTests/PersonalSignatureTests.swift index e9b99f965..418ff7a22 100755 --- a/Tests/web3swiftTests/localTests/PersonalSignatureTests.swift +++ b/Tests/web3swiftTests/localTests/PersonalSignatureTests.swift @@ -42,7 +42,7 @@ class PersonalSignatureTests: XCTestCase { let allAddresses = try await web3.eth.ownedAccounts() deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let deployResult = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let deployResult = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = Data.fromHex(deployResult.hash.stripHexPrefix())! Thread.sleep(forTimeInterval: 1.0) diff --git a/Tests/web3swiftTests/localTests/TestHelpers.swift b/Tests/web3swiftTests/localTests/TestHelpers.swift index 23ad080a3..c80b4435e 100644 --- a/Tests/web3swiftTests/localTests/TestHelpers.swift +++ b/Tests/web3swiftTests/localTests/TestHelpers.swift @@ -33,7 +33,7 @@ class TestHelpers { parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = Data.fromHex(result.hash.stripHexPrefix())! Thread.sleep(forTimeInterval: 1.0) diff --git a/Tests/web3swiftTests/localTests/UserCases.swift b/Tests/web3swiftTests/localTests/UserCases.swift index e353fb990..1f930783d 100755 --- a/Tests/web3swiftTests/localTests/UserCases.swift +++ b/Tests/web3swiftTests/localTests/UserCases.swift @@ -76,11 +76,11 @@ class UserCases: XCTestCase { let allAddresses = try await web3.eth.ownedAccounts() let contract = web3.contract(Web3.Utils.estimateGasTestABI, at: nil, abiVersion: 2)! - let parameters = [] as [AnyObject] + let parameters = [AnyObject]() let deployTx = contract.prepareDeploy(bytecode: bytecode, parameters: parameters)! deployTx.transaction.from = allAddresses[0] let policies = Policies(gasLimitPolicy: .manual(3000000)) - let result = try await deployTx.writeToChain(password: "web3swift", policies: policies) + let result = try await deployTx.writeToChain(password: "web3swift", policies: policies, sendRaw: false) let txHash = Data.fromHex(result.hash.stripHexPrefix())! Thread.sleep(forTimeInterval: 1.0) From 5e1dd2c9d4272f95c0a1d26d173d375dc1091931 Mon Sep 17 00:00:00 2001 From: "pharms.eth" <100330083+pharms-eth@users.noreply.github.com> Date: Thu, 24 Nov 2022 10:51:45 -0800 Subject: [PATCH 11/30] fix another test for deploy changes --- Tests/web3swiftTests/localTests/TransactionsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/TransactionsTests.swift b/Tests/web3swiftTests/localTests/TransactionsTests.swift index 893e3b13c..b7e1a6886 100755 --- a/Tests/web3swiftTests/localTests/TransactionsTests.swift +++ b/Tests/web3swiftTests/localTests/TransactionsTests.swift @@ -638,7 +638,7 @@ class TransactionsTests: XCTestCase { writeTX.transaction.from = from writeTX.transaction.value = value! let policies = Policies(gasLimitPolicy: .manual(78423)) - let result = try await writeTX.writeToChain(password: "", policies: policies) + let result = try await writeTX.writeToChain(password: "", policies: policies, sendRaw: false) let txHash = Data.fromHex(result.hash.stripHexPrefix())! print("Transaction with hash ", txHash) From 624762838c3306f119f6a0d16fe7cec31060e3a6 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu <36865532+JeneaVranceanu@users.noreply.github.com> Date: Fri, 25 Nov 2022 14:47:41 +0200 Subject: [PATCH 12/30] chore: default value of sendRaw for writeToChain is true --- Sources/web3swift/Operations/WriteOperation.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/web3swift/Operations/WriteOperation.swift b/Sources/web3swift/Operations/WriteOperation.swift index c380c64f9..6458ba6ff 100755 --- a/Sources/web3swift/Operations/WriteOperation.swift +++ b/Sources/web3swift/Operations/WriteOperation.swift @@ -19,7 +19,7 @@ public class WriteOperation: ReadOperation { /// nonce are resolved. Default value is ``Policies/auto``. /// - sendRaw: If set to `true` transaction will be signed and sent using `eth_sendRawTransaction`. /// Otherwise, no signing attempts will take place and the `eth_sendTransaction` RPC will be used instead. - /// Default value is `false`. + /// Default value is `true`. public func writeToChain(password: String, policies: Policies = .auto, sendRaw: Bool = true) async throws -> TransactionSendingResult { try await policyResolver.resolveAll(for: &transaction, with: policies) From aa32c36f193dc12b607c6de597d446ae8a6b689c Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Mon, 28 Nov 2022 17:37:15 +0200 Subject: [PATCH 13/30] chore: decodeReturnData refactoring + documentation for it --- Sources/Core/EthereumABI/ABIElements.swift | 117 +++++++++++---------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index bb269b9c5..352dfa2e5 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -264,72 +264,75 @@ extension ABI.Element.Function { return Core.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs) } - public func decodeReturnData(_ data: Data) -> [String: Any]? { - // the response size greater than equal 100 bytes, when read function aborted by "require" statement. - // if "require" statement has no message argument, the response is empty (0 byte). - if data.bytes.count >= 100 { - let check00_31 = BigUInt("08C379A000000000000000000000000000000000000000000000000000000000", radix: 16)! - let check32_63 = BigUInt("0000002000000000000000000000000000000000000000000000000000000000", radix: 16)! - - // check data[00-31] and data[32-63] - if check00_31 == BigUInt(data[0...31]) && check32_63 == BigUInt(data[32...63]) { - // data.bytes[64-67] contains the length of require message - let len = (Int(data.bytes[64])<<24) | (Int(data.bytes[65])<<16) | (Int(data.bytes[66])<<8) | Int(data.bytes[67]) - - let message = String(bytes: data.bytes[68..<(68+len)], encoding: .utf8)! - - print("read function aborted by require statement: \(message)") - - var returnArray = [String: Any]() - - // set infomation - returnArray["_abortedByRequire"] = true - returnArray["_errorMessageFromRequire"] = message - - // set empty values - for i in 0 ..< outputs.count { - let name = "\(i)" - returnArray[name] = outputs[i].type.emptyValue - if outputs[i].name != "" { - returnArray[outputs[i].name] = outputs[i].type.emptyValue - } - } + /// Decodes data returned by a function call. Able to decode `revert("...")` calls. + /// - Parameter data: bytes returned by a function call. + /// - Returns: a dictionary containing returned data mappend to indices and names of returned values if these are not `nil`. + /// Return cases: + /// - when no `outputs` declared: returning `["_success": true]`; + /// - when `outputs` declared and decoding completed successfully: returning `["_success": true, "0": value_1, "1": value_2, ...]`. + /// Additionally this dictionary will have mappings to output names if these names are specified in the ABI. + /// - function call was aborted using `require(some_string_error_message)`: returning `["_success": false, "_abortedByRequire": true, "_errorMessageFromRequire": error_message]`. + /// - in case of any error: returning `["_success": false, "_failureReason": String]`; + /// Error reasons include: + /// - `outputs` declared but at least one value failed to be decoded; + /// - `data.count` is less than `outputs.count * 32`; + /// - `outputs` defined and `data` is empty; + /// - `data` represent reverted transaction + public func decodeReturnData(_ data: Data) -> [String: Any] { + guard !outputs.isEmpty else { + NSLog("Function doesn't have any output types to decode given data.") + return ["_success": true] + } - return returnArray - } + /// If data is empty and outputs are expected it is treated as a `requite(expression)` call with no message. + /// In solidity `require(expression)` call, if `expresison` returns `false`, results in an empty response. + if data.count == 0 && !outputs.isEmpty { + return ["_success": false, "_failureReason": "Cannot decode empty data. \(outputs.count) outputs are expected: \(outputs.map { $0.type.abiRepresentation }). Was this a result of en empty `require(expression)` call?"] } - var returnArray = [String: Any]() + guard outputs.count * 32 <= data.count else { + return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."] + } - // the "require" statement with no message argument will be caught here - if data.count == 0 && outputs.count == 1 { - let name = "0" - let value = outputs[0].type.emptyValue - returnArray[name] = value - if outputs[0].name != "" { - returnArray[outputs[0].name] = value - } - } else { - guard outputs.count * 32 <= data.count else { return nil } - - var i = 0 - guard let values = ABIDecoder.decode(types: outputs, data: data) else { return nil } - for output in outputs { - let name = "\(i)" - returnArray[name] = values[i] - if output.name != "" { - returnArray[output.name] = values[i] + /// How `require(expression, string)` return value is decomposed: + /// - `08C379A0` function selector for Error(string); + /// - next 32 bytes are the data offset; + /// - next 32 bytes are the error message length; + /// - the next N bytes, where N is the int value + /// + /// Data offset must be present. Hexadecimal value of `0000...0020` is 32 in decimal. Reasoning for `BigInt(...) == 32`. + if data[0..<4] == Data.fromHex("08C379A0"), + data.bytes.count >= 100, + BigInt(data[4..<36]) == 32, + let messageLength = Int(Data(data[36..<68]).toHexString(), radix: 16), + let message = String(bytes: data.bytes[68..<(68+messageLength)], encoding: .utf8) { + var returnArray: [String: Any] = ["_success": false, + "_failureReason": "`require` was executed.", + "_abortedByRequire": true, + "_errorMessageFromRequire": message] + + // set empty values + for i in outputs.indices { + returnArray["\(i)"] = outputs[i].type.emptyValue + if !outputs[i].name.isEmpty { + returnArray[outputs[i].name] = outputs[i].type.emptyValue } - i = i + 1 } - // set a flag to detect the request succeeded - } - if returnArray.isEmpty && !outputs.isEmpty { - return nil + return returnArray } - returnArray["_success"] = true + // TODO: need improvement - we should be able to tell which value failed to be decoded + guard let values = ABIDecoder.decode(types: outputs, data: data) else { + return ["_success": false, "_failureReason": "Failed to decode at least one value."] + } + var returnArray: [String: Any] = ["_success": true] + for i in outputs.indices { + returnArray["\(i)"] = values[i] + if !outputs[i].name.isEmpty { + returnArray[outputs[i].name] = values[i] + } + } return returnArray } } From a5c56ea94a5f6850e600dbfae4cb929b90520e36 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 29 Nov 2022 17:47:30 +0200 Subject: [PATCH 14/30] fix: first we try to decode data as a revert/require call --- Sources/Core/EthereumABI/ABIElements.swift | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index 352dfa2e5..d55c6fd1f 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -264,7 +264,7 @@ extension ABI.Element.Function { return Core.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs) } - /// Decodes data returned by a function call. Able to decode `revert("...")` calls. + /// Decodes data returned by a function call. Able to decode `revert(message)`, `revert CustomError(...)` and `require(expression, message)` calls. /// - Parameter data: bytes returned by a function call. /// - Returns: a dictionary containing returned data mappend to indices and names of returned values if these are not `nil`. /// Return cases: @@ -279,21 +279,6 @@ extension ABI.Element.Function { /// - `outputs` defined and `data` is empty; /// - `data` represent reverted transaction public func decodeReturnData(_ data: Data) -> [String: Any] { - guard !outputs.isEmpty else { - NSLog("Function doesn't have any output types to decode given data.") - return ["_success": true] - } - - /// If data is empty and outputs are expected it is treated as a `requite(expression)` call with no message. - /// In solidity `require(expression)` call, if `expresison` returns `false`, results in an empty response. - if data.count == 0 && !outputs.isEmpty { - return ["_success": false, "_failureReason": "Cannot decode empty data. \(outputs.count) outputs are expected: \(outputs.map { $0.type.abiRepresentation }). Was this a result of en empty `require(expression)` call?"] - } - - guard outputs.count * 32 <= data.count else { - return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."] - } - /// How `require(expression, string)` return value is decomposed: /// - `08C379A0` function selector for Error(string); /// - next 32 bytes are the data offset; @@ -301,8 +286,8 @@ extension ABI.Element.Function { /// - the next N bytes, where N is the int value /// /// Data offset must be present. Hexadecimal value of `0000...0020` is 32 in decimal. Reasoning for `BigInt(...) == 32`. - if data[0..<4] == Data.fromHex("08C379A0"), - data.bytes.count >= 100, + if data.bytes.count >= 100, + data[0..<4] == Data.fromHex("08C379A0"), BigInt(data[4..<36]) == 32, let messageLength = Int(Data(data[36..<68]).toHexString(), radix: 16), let message = String(bytes: data.bytes[68..<(68+messageLength)], encoding: .utf8) { @@ -322,6 +307,21 @@ extension ABI.Element.Function { return returnArray } + guard !outputs.isEmpty else { + NSLog("Function doesn't have any output types to decode given data.") + return ["_success": true] + } + + /// If data is empty and outputs are expected it is treated as a `requite(expression)` call with no message. + /// In solidity `require(expression)` call, if `expresison` returns `false`, results in an empty response. + if data.count == 0 && !outputs.isEmpty { + return ["_success": false, "_failureReason": "Cannot decode empty data. \(outputs.count) outputs are expected: \(outputs.map { $0.type.abiRepresentation }). Was this a result of en empty `require(expression)` call?"] + } + + guard outputs.count * 32 <= data.count else { + return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."] + } + // TODO: need improvement - we should be able to tell which value failed to be decoded guard let values = ABIDecoder.decode(types: outputs, data: data) else { return ["_success": false, "_failureReason": "Failed to decode at least one value."] From 3198752178e17c6f97d96c3602c9a90c3a02d6b9 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 29 Nov 2022 19:36:24 +0200 Subject: [PATCH 15/30] chore: added trim extension function to String --- Sources/Core/EthereumABI/ABIElements.swift | 9 +++++---- Sources/Core/Utility/String+Extension.swift | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index d55c6fd1f..163212377 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -77,7 +77,7 @@ public extension ABI { public let type: ParameterType public init(name: String, type: ParameterType) { - self.name = name + self.name = name.trim() self.type = type } } @@ -91,7 +91,7 @@ public extension ABI { public let payable: Bool public init(name: String?, inputs: [InOut], outputs: [InOut], constant: Bool, payable: Bool) { - self.name = name + self.name = name?.trim() self.inputs = inputs self.outputs = outputs self.constant = constant @@ -103,6 +103,7 @@ public extension ABI { public let inputs: [InOut] public let constant: Bool public let payable: Bool + public init(inputs: [InOut], constant: Bool, payable: Bool) { self.inputs = inputs self.constant = constant @@ -126,7 +127,7 @@ public extension ABI { public let anonymous: Bool public init(name: String, inputs: [Input], anonymous: Bool) { - self.name = name + self.name = name.trim() self.inputs = inputs self.anonymous = anonymous } @@ -137,7 +138,7 @@ public extension ABI { public let indexed: Bool public init(name: String, type: ParameterType, indexed: Bool) { - self.name = name + self.name = name.trim() self.type = type self.indexed = indexed } diff --git a/Sources/Core/Utility/String+Extension.swift b/Sources/Core/Utility/String+Extension.swift index 8bbe1f688..cf7537663 100755 --- a/Sources/Core/Utility/String+Extension.swift +++ b/Sources/Core/Utility/String+Extension.swift @@ -130,6 +130,11 @@ extension String { return Int(s[s.startIndex].value) } } + + /// Strips whitespaces and newlines on both ends. + func trim() -> String { + trimmingCharacters(in: .whitespacesAndNewlines) + } } extension Character { From f2cb4b9167ad9ee3be82474727d328c4db7d45e5 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 29 Nov 2022 19:38:06 +0200 Subject: [PATCH 16/30] fix: EthError uses InOut as type for inputs; produces error declaration using `errorDeclaration` computed variable; tests added --- Sources/Core/EthereumABI/ABIElements.swift | 16 +++--- Sources/Core/EthereumABI/ABIParsing.swift | 14 ++--- .../localTests/ABIElementsTests.swift | 54 +++++++++++++++++++ 3 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/ABIElementsTests.swift diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index 163212377..aa9a8f2e9 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -156,16 +156,16 @@ public extension ABI { /// Custom structured error type available since solidity 0.8.4 public struct EthError { public let name: String - public let inputs: [Input] + public let inputs: [InOut] - public struct Input { - public let name: String - public let type: ParameterType + /// e.g. `CustomError(uint32, address sender)` + public var errorDeclaration: String { + "\(name)(\(inputs.map { "\($0.type.abiRepresentation) \($0.name)".trim() }.joined(separator: ",")))" + } - public init(name: String, type: ParameterType) { - self.name = name - self.type = type - } + public init(name: String, inputs: [InOut]) { + self.name = name.trim() + self.inputs = inputs } } } diff --git a/Sources/Core/EthereumABI/ABIParsing.swift b/Sources/Core/EthereumABI/ABIParsing.swift index 1e22e55f0..c4e68aaef 100755 --- a/Sources/Core/EthereumABI/ABIParsing.swift +++ b/Sources/Core/EthereumABI/ABIParsing.swift @@ -129,11 +129,9 @@ private func parseReceive(abiRecord: ABI.Record) throws -> ABI.Element.Receive { } private func parseError(abiRecord: ABI.Record) throws -> ABI.Element.EthError { - let inputs = try abiRecord.inputs?.map({ (input: ABI.Input) throws -> ABI.Element.EthError.Input in - let nativeInput = try input.parseForError() - return nativeInput - }) - let abiInputs = inputs ?? [] + let abiInputs = try abiRecord.inputs?.map({ input throws -> ABI.Element.InOut in + try input.parse() + }) ?? [] let name = abiRecord.name ?? "" return ABI.Element.EthError(name: name, inputs: abiInputs) } @@ -172,12 +170,6 @@ extension ABI.Input { let indexed = self.indexed == true return ABI.Element.Event.Input(name: name, type: parameterType, indexed: indexed) } - - func parseForError() throws -> ABI.Element.EthError.Input { - let name = self.name ?? "" - let parameterType = try ABITypeParser.parseTypeString(self.type) - return ABI.Element.EthError.Input(name: name, type: parameterType) - } } extension ABI.Output { diff --git a/Tests/web3swiftTests/localTests/ABIElementsTests.swift b/Tests/web3swiftTests/localTests/ABIElementsTests.swift new file mode 100644 index 000000000..51ab9f2ae --- /dev/null +++ b/Tests/web3swiftTests/localTests/ABIElementsTests.swift @@ -0,0 +1,54 @@ +// +// ABIElementsTest.swift +// +// Created by JeneaVranceanu on 28.11.2022. +// + +import Foundation +import XCTest +import Core + +class ABIElementsTest: XCTestCase { + typealias EthError = ABI.Element.EthError + + /// Function with any parameters should be able to decode `require` and `revert` calls in soliditiy. + /// Note: `require(expression)` and `revert()` without a message return 0 bytes thus we cannot guarantee + /// that 0 bytes response will be interpreted correctly. + private let emptyFunction = ABI.Element.Function(name: "any", + inputs: [], + outputs: [], + constant: false, + payable: false) + + func testErrorRepresentation() { + XCTAssertEqual(EthError(name: "Error", inputs: []).errorDeclaration, "Error()") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "", type: .address)]).errorDeclaration, "Error(address)") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address)]).errorDeclaration, "Error(address)") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address), .init(name: "", type: .uint(bits: 256))]).errorDeclaration, "Error(address,uint256)") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "sender", type: .address), .init(name: " ", type: .uint(bits: 256))]).errorDeclaration, "Error(address sender,uint256)") + let allTypesNamedAndNot: [ABI.Element.InOut] = [ + .init(name: "sender", type: .address), + .init(name: "", type: .address), + .init(name: "", type: .uint(bits: 8)), + .init(name: "", type: .uint(bits: 16)), + .init(name: "", type: .uint(bits: 32)), + .init(name: "", type: .uint(bits: 64)), + .init(name: "", type: .uint(bits: 128)), + .init(name: "", type: .uint(bits: 256)), + .init(name: "my_int_8", type: .int(bits: 8)), + .init(name: "my_int_16", type: .int(bits: 16)), + .init(name: "my_int_32", type: .int(bits: 32)), + .init(name: "my_int_64", type: .int(bits: 64)), + .init(name: "my_int_128", type: .int(bits: 128)), + .init(name: "my_int_256", type: .int(bits: 256)), + .init(name: "someFlag", type: .bool), + .init(name: "rand_bytes", type: .bytes(length: 123)), + .init(name: "", type: .dynamicBytes), + .init(name: "error_message_maybe", type: .string), + ] + XCTAssertEqual(EthError(name: "VeryCustomErrorName", + inputs: allTypesNamedAndNot).errorDeclaration, + "VeryCustomErrorName(address sender,address,uint8,uint16,uint32,uint64,uint128,uint256,int8 my_int_8,int16 my_int_16,int32 my_int_32,int64 my_int_64,int128 my_int_128,int256 my_int_256,bool someFlag,bytes123 rand_bytes,bytes,string error_message_maybe)") + } + +} From 6bc51005ea7ad0fd2792a949f9a245138cc1cdf3 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 29 Nov 2022 21:24:27 +0200 Subject: [PATCH 17/30] chore: test update - added array type to error arguments --- Tests/web3swiftTests/localTests/ABIElementsTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/web3swiftTests/localTests/ABIElementsTests.swift b/Tests/web3swiftTests/localTests/ABIElementsTests.swift index 51ab9f2ae..4cd1a5eb3 100644 --- a/Tests/web3swiftTests/localTests/ABIElementsTests.swift +++ b/Tests/web3swiftTests/localTests/ABIElementsTests.swift @@ -26,6 +26,7 @@ class ABIElementsTest: XCTestCase { XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address)]).errorDeclaration, "Error(address)") XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address), .init(name: "", type: .uint(bits: 256))]).errorDeclaration, "Error(address,uint256)") XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "sender", type: .address), .init(name: " ", type: .uint(bits: 256))]).errorDeclaration, "Error(address sender,uint256)") + // Not all types are supported in errors, e.g. tuples and functions are not supported let allTypesNamedAndNot: [ABI.Element.InOut] = [ .init(name: "sender", type: .address), .init(name: "", type: .address), @@ -44,11 +45,12 @@ class ABIElementsTest: XCTestCase { .init(name: "someFlag", type: .bool), .init(name: "rand_bytes", type: .bytes(length: 123)), .init(name: "", type: .dynamicBytes), + .init(name: "arrarrarray123", type: .array(type: .bool, length: 0)), .init(name: "error_message_maybe", type: .string), ] XCTAssertEqual(EthError(name: "VeryCustomErrorName", inputs: allTypesNamedAndNot).errorDeclaration, - "VeryCustomErrorName(address sender,address,uint8,uint16,uint32,uint64,uint128,uint256,int8 my_int_8,int16 my_int_16,int32 my_int_32,int64 my_int_64,int128 my_int_128,int256 my_int_256,bool someFlag,bytes123 rand_bytes,bytes,string error_message_maybe)") + "VeryCustomErrorName(address sender,address,uint8,uint16,uint32,uint64,uint128,uint256,int8 my_int_8,int16 my_int_16,int32 my_int_32,int64 my_int_64,int128 my_int_128,int256 my_int_256,bool someFlag,bytes123 rand_bytes,bytes,bool[] arrarrarray123,string error_message_maybe)") } } From 7257f591c562713382e7278c0cb48d7671993e21 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 1 Dec 2022 16:54:43 +0200 Subject: [PATCH 18/30] chore: EthError has default empty inputs array --- Sources/Core/EthereumABI/ABIElements.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index aa9a8f2e9..3ec83b03e 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -163,7 +163,7 @@ public extension ABI { "\(name)(\(inputs.map { "\($0.type.abiRepresentation) \($0.name)".trim() }.joined(separator: ",")))" } - public init(name: String, inputs: [InOut]) { + public init(name: String, inputs: [InOut] = []) { self.name = name.trim() self.inputs = inputs } From 31516787d017ca2c70df759de02d8be642685b83 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 1 Dec 2022 16:55:21 +0200 Subject: [PATCH 19/30] feat: decodeReturnData is split into decodeReturnData and decodeErrorResponse --- Sources/Core/EthereumABI/ABIElements.swift | 184 +++++++++++++++------ 1 file changed, 138 insertions(+), 46 deletions(-) diff --git a/Sources/Core/EthereumABI/ABIElements.swift b/Sources/Core/EthereumABI/ABIElements.swift index 3ec83b03e..21217af3d 100755 --- a/Sources/Core/EthereumABI/ABIElements.swift +++ b/Sources/Core/EthereumABI/ABIElements.swift @@ -265,47 +265,58 @@ extension ABI.Element.Function { return Core.decodeInputData(rawData, methodEncoding: methodEncoding, inputs: inputs) } - /// Decodes data returned by a function call. Able to decode `revert(message)`, `revert CustomError(...)` and `require(expression, message)` calls. - /// - Parameter data: bytes returned by a function call. - /// - Returns: a dictionary containing returned data mappend to indices and names of returned values if these are not `nil`. + /// Decodes data returned by a function call. Able to decode `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls. + /// - Parameters: + /// - data: bytes returned by a function call; + /// - errors: optional dictionary of known errors that could be returned by the function you called. Used to decode the error information. + /// - Returns: a dictionary containing decoded data mappend to indices and names of returned values if these are not `nil`. + /// If `data` is an error response returns dictionary containing all available information about that specific error. Read more for details. + /// /// Return cases: - /// - when no `outputs` declared: returning `["_success": true]`; - /// - when `outputs` declared and decoding completed successfully: returning `["_success": true, "0": value_1, "1": value_2, ...]`. - /// Additionally this dictionary will have mappings to output names if these names are specified in the ABI. - /// - function call was aborted using `require(some_string_error_message)`: returning `["_success": false, "_abortedByRequire": true, "_errorMessageFromRequire": error_message]`. - /// - in case of any error: returning `["_success": false, "_failureReason": String]`; - /// Error reasons include: - /// - `outputs` declared but at least one value failed to be decoded; - /// - `data.count` is less than `outputs.count * 32`; - /// - `outputs` defined and `data` is empty; - /// - `data` represent reverted transaction - public func decodeReturnData(_ data: Data) -> [String: Any] { - /// How `require(expression, string)` return value is decomposed: - /// - `08C379A0` function selector for Error(string); - /// - next 32 bytes are the data offset; - /// - next 32 bytes are the error message length; - /// - the next N bytes, where N is the int value - /// - /// Data offset must be present. Hexadecimal value of `0000...0020` is 32 in decimal. Reasoning for `BigInt(...) == 32`. - if data.bytes.count >= 100, - data[0..<4] == Data.fromHex("08C379A0"), - BigInt(data[4..<36]) == 32, - let messageLength = Int(Data(data[36..<68]).toHexString(), radix: 16), - let message = String(bytes: data.bytes[68..<(68+messageLength)], encoding: .utf8) { - var returnArray: [String: Any] = ["_success": false, - "_failureReason": "`require` was executed.", - "_abortedByRequire": true, - "_errorMessageFromRequire": message] - - // set empty values - for i in outputs.indices { - returnArray["\(i)"] = outputs[i].type.emptyValue - if !outputs[i].name.isEmpty { - returnArray[outputs[i].name] = outputs[i].type.emptyValue - } - } - - return returnArray + /// - when no `outputs` declared and `data` is not an error response: + ///```swift + ///["_success": true] + ///``` + /// - when `outputs` declared and decoding completed successfully: + ///```swift + ///["_success": true, "0": value_1, "1": value_2, ...] + ///``` + ///Additionally this dictionary will have mappings to output names if these names are specified in the ABI; + /// - function call was aborted using `revert(message)` or `require(expression, message)`: + ///```swift + ///["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]` + ///``` + /// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type: + ///```swift + ///["_success": false, + ///"_abortedByRevertOrRequire": true, + ///"_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)` + ///"0": error_arg1, + ///"1": error_arg2, + ///..., + ///"error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress` + ///"error_arg2_name": error_arg2, // Otherwise, you can query them by position index. + ///...] + ///``` + ///- in case of any error: + ///```swift + ///["_success": false, "_failureReason": String] + ///``` + ///Error reasons include: + /// - `outputs` declared but at least one value failed to be decoded; + /// - `data.count` is less than `outputs.count * 32`; + /// - `outputs` defined and `data` is empty; + /// - `data` represent reverted transaction + /// + /// How `revert(string)` and `require(expression, string)` return value is decomposed: + /// - `08C379A0` function selector for `Error(string)`; + /// - next 32 bytes are the data offset; + /// - next 32 bytes are the error message length; + /// - the next N bytes, where N >= 32, are the message bytes + /// - the rest are 0 bytes padding. + public func decodeReturnData(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any] { + if let decodedError = decodeErrorResponse(data, errors: errors) { + return decodedError } guard !outputs.isEmpty else { @@ -313,12 +324,6 @@ extension ABI.Element.Function { return ["_success": true] } - /// If data is empty and outputs are expected it is treated as a `requite(expression)` call with no message. - /// In solidity `require(expression)` call, if `expresison` returns `false`, results in an empty response. - if data.count == 0 && !outputs.isEmpty { - return ["_success": false, "_failureReason": "Cannot decode empty data. \(outputs.count) outputs are expected: \(outputs.map { $0.type.abiRepresentation }). Was this a result of en empty `require(expression)` call?"] - } - guard outputs.count * 32 <= data.count else { return ["_success": false, "_failureReason": "Bytes count must be at least \(outputs.count * 32). Given \(data.count). Decoding will fail."] } @@ -336,6 +341,93 @@ extension ABI.Element.Function { } return returnArray } + + /// Decodes `revert(string)`, `revert CustomError(...)` and `require(expression, string)` calls. + /// If `data` is empty and `outputs` are not empty it's considered that data is a result of `revert()` or `require(false)`. + /// - Parameters: + /// - data: returned function call data to decode; + /// - errors: optional known errors that could be thrown by the function you called. + /// - Returns: dictionary containing information about the error thrown by the function call. + /// + /// What could be returned: + /// - `nil` if data doesn't represent an error or it failed to be mapped to any of the `errors` or `Error(string)` types; + /// - `nil` is `data.isEmpty` and `outputs.isEmpty`; + /// - `data.isEmpty` and `!outputs.isEmpty`: + /// ```swift + /// ["_success": false, + /// "_failureReason": "Cannot decode empty data. X outputs are expected: [outputs_types]. Was this a result of en empty `require(false)` or `revert()` call?"] + /// ``` + /// - function call was aborted using `revert(message)` or `require(expression, message)`: + /// ```swift + /// ["_success": false, "_abortedByRevertOrRequire": true, "_errorMessage": message]` + /// ``` + /// - function call was aborted using `revert CustomMessage()` and `errors` argument contains the ABI of that custom error type: + /// ```swift + /// ["_success": false, + /// "_abortedByRevertOrRequire": true, + /// "_error": error_name_and_types, // e.g. `MyCustomError(uint256, address senderAddress)` + /// "0": error_arg1, + /// "1": error_arg2, + /// ..., + /// "error_arg1_name": error_arg1, // Only named arguments will be mapped to their names, e.g. `"senderAddress": EthereumAddress` + /// "error_arg2_name": error_arg2, // Otherwise, you can query them by position index. + /// ...] + /// + /// /// or if custo error found but decoding failed + /// ["_success": false, + /// "_abortedByRevertOrRequire": true, + /// // "_error" can contain value like `MyCustomError(uint256, address senderAddress)` + /// "_error": error_name_and_types, + /// // "_parsingError" is optional and is present only if decoding of custom error arguments failed + /// "_parsingError": "Data matches MyCustomError(uint256, address senderAddress) but failed to be decoded."] + /// ``` + public func decodeErrorResponse(_ data: Data, errors: [String: ABI.Element.EthError]? = nil) -> [String: Any]? { + /// If data is empty and outputs are expected it is treated as a `require(expression)` or `revert()` call with no message. + /// In solidity `require(false)` and `revert()` calls return empty error response. + if data.isEmpty && !outputs.isEmpty { + return ["_success": false, "_failureReason": "Cannot decode empty data. \(outputs.count) outputs are expected: \(outputs.map { $0.type.abiRepresentation }). Was this a result of en empty `require(false)` or `revert()` call?"] + } + + /// Explanation of this condition: + /// When `revert(string)` or `require(false, string)` are called in soliditiy they produce + /// an error, specifically an instance of default `Error(string)` type. + /// 1) The total number of bytes returned are at least 100. + /// 2) The function selector for `Error(string)` is `08C379A0`; + /// 3) Data offset must be present. Hexadecimal value of `0000...0020` is 32 in decimal. Reasoning for `BigInt(...) == 32`. + /// 4) `messageLength` is used to determine where message bytes end to decode string correctly. + /// 5) The rest of the `data` must be 0 bytes or empty. + if data.bytes.count >= 100, + Data(data[0..<4]) == Data.fromHex("08C379A0"), + BigInt(data[4..<36]) == 32, + let messageLength = Int(Data(data[36..<68]).toHexString(), radix: 16), + let message = String(bytes: data.bytes[68..<(68+messageLength)], encoding: .utf8), + (68+messageLength == data.count || data.bytes[68+messageLength..= 4, + let errors = errors, + let customError = errors[data[0..<4].toHexString().stripHexPrefix()] { + var errorResponse: [String: Any] = ["_success": false, "_abortedByRevertOrRequire": true, "_error": customError.errorDeclaration] + + if (data.count > 32 && !customError.inputs.isEmpty), + let decodedInputs = ABIDecoder.decode(types: customError.inputs, data: Data(data[4.. Date: Thu, 1 Dec 2022 16:57:18 +0200 Subject: [PATCH 20/30] chore: tests for errors decode --- .../ABIElementErrorDecodingTest.swift | 187 ++++++++++++++++++ .../localTests/ABIElementsTests.swift | 56 ------ 2 files changed, 187 insertions(+), 56 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift delete mode 100644 Tests/web3swiftTests/localTests/ABIElementsTests.swift diff --git a/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift new file mode 100644 index 000000000..9b4308151 --- /dev/null +++ b/Tests/web3swiftTests/localTests/ABIElementErrorDecodingTest.swift @@ -0,0 +1,187 @@ +// +// ABIElementErrorDecodingTest.swift +// +// Created by JeneaVranceanu on 28.11.2022. +// + +import Foundation +import XCTest +import Core + +class ABIElementErrorDecodingTest: XCTestCase { + typealias EthError = ABI.Element.EthError + + /// Function with any parameters should be able to decode `require` and `revert` calls in soliditiy. + /// Note: `require(expression)` and `revert()` without a message return 0 bytes thus we cannot guarantee + /// that 0 bytes response will be interpreted correctly. + private let emptyFunction = ABI.Element.Function(name: "any", + inputs: [], + outputs: [], + constant: false, + payable: false) + private let oneOutputFunction = ABI.Element.Function(name: "any", + inputs: [], + outputs: [.init(name: "", type: .bool)], + constant: false, + payable: false) + + func testErrorRepresentation() { + XCTAssertEqual(EthError(name: "Error", inputs: []).errorDeclaration, "Error()") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "", type: .address)]).errorDeclaration, "Error(address)") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address)]).errorDeclaration, "Error(address)") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address), .init(name: "", type: .uint(bits: 256))]).errorDeclaration, "Error(address,uint256)") + XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "sender", type: .address), .init(name: " ", type: .uint(bits: 256))]).errorDeclaration, "Error(address sender,uint256)") + // Not all types are supported in errors, e.g. tuples and functions are not supported + let allTypesNamedAndNot: [ABI.Element.InOut] = [ + .init(name: "sender", type: .address), + .init(name: "", type: .address), + .init(name: "", type: .uint(bits: 8)), + .init(name: "", type: .uint(bits: 16)), + .init(name: "", type: .uint(bits: 32)), + .init(name: "", type: .uint(bits: 64)), + .init(name: "", type: .uint(bits: 128)), + .init(name: "", type: .uint(bits: 256)), + .init(name: "my_int_8", type: .int(bits: 8)), + .init(name: "my_int_16", type: .int(bits: 16)), + .init(name: "my_int_32", type: .int(bits: 32)), + .init(name: "my_int_64", type: .int(bits: 64)), + .init(name: "my_int_128", type: .int(bits: 128)), + .init(name: "my_int_256", type: .int(bits: 256)), + .init(name: "someFlag", type: .bool), + .init(name: "rand_bytes", type: .bytes(length: 123)), + .init(name: "", type: .dynamicBytes), + .init(name: "arrarrarray123", type: .array(type: .bool, length: 0)), + .init(name: "error_message_maybe", type: .string), + ] + XCTAssertEqual(EthError(name: "VeryCustomErrorName", + inputs: allTypesNamedAndNot).errorDeclaration, + "VeryCustomErrorName(address sender,address,uint8,uint16,uint32,uint64,uint128,uint256,int8 my_int_8,int16 my_int_16,int32 my_int_32,int64 my_int_64,int128 my_int_128,int256 my_int_256,bool someFlag,bytes123 rand_bytes,bytes,bool[] arrarrarray123,string error_message_maybe)") + } + + /// Empty Data is not decoded as a call of `revert` or `require` if function has no outputs. + /// If a function that has no outputs attempts to decode empty `revert` or `require` must return `nil` + /// because we don't know just based on the output if the call was successful or reverted. + func testDecodeEmptyErrorOnNoOutputFunction() { + XCTAssertTrue(emptyFunction.decodeErrorResponse(Data()) == nil) + } + + func testDecodeEmptyErrorOnOneOutputFunction() { + guard let errorData = oneOutputFunction.decodeErrorResponse(Data()) else { + XCTFail("Empty Data must be decoded as a `revert()` or `require(false)` call if function used to decode it has at least one output parameter.") + return + } + + XCTAssertEqual(errorData["_success"] as? Bool, false) + XCTAssertNotNil(errorData["_failureReason"] as? String) + + let decodedOutput = oneOutputFunction.decodeReturnData(Data()) + + XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) + XCTAssertEqual(errorData["_failureReason"] as? String, decodedOutput["_failureReason"] as? String) + } + + /// Data is decoded as a call of `revert` or `require` with a message no matter the number of outputs configured in the ``ABI/Element/Function``. + /// `revert(message)` and `require(false,message)`return at least 128 bytes. We cannot differentiate between `require` or `revert`. + func testDecodeDefaultErrorWithMessage() { + /// 08c379a0 - Error(string) function selector + /// 0000000000000000000000000000000000000000000000000000000000000020 - Data offset + /// 000000000000000000000000000000000000000000000000000000000000001a - Message length + /// 4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 - Message + 0 bytes padding + /// 0000... - some more 0 bytes padding to make the number of bytes match 32 bytes chunks + let errorResponse = Data.fromHex("08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a4e6f7420656e6f7567682045746865722070726f76696465642e00000000000000000000000000000000000000000000000000000000000000000000")! + guard let errorData = emptyFunction.decodeErrorResponse(errorResponse) else { + XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") + return + } + + XCTAssertEqual(errorData["_success"] as? Bool, false) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) + XCTAssertEqual(errorData["_errorMessage"] as? String, "Not enough Ether provided.") + XCTAssertNotNil(errorData["_failureReason"] as? String) + + let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse) + + XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) + XCTAssertEqual(errorData["_failureReason"] as? String, decodedOutput["_failureReason"] as? String) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) + XCTAssertEqual(errorData["_errorMessage"] as? String, decodedOutput["_errorMessage"] as? String) + XCTAssertEqual(decodedOutput["_errorMessage"] as? String, "Not enough Ether provided.") + } + + /// Data is decoded as a call of `revert Unauthorized()`. Decoded only if custom error ABI is given. + func testDecodeRevertWithCustomError() { + /// 82b42900 - Unauthorized() function selector + /// 00000000000000000000000000000000000000000000000000000000 - padding bytes + let errorResponse = Data.fromHex("82b4290000000000000000000000000000000000000000000000000000000000")! + let errors: [String: EthError] = ["82b42900" : .init(name: "Unauthorized", inputs: [])] + guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { + XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") + return + } + + XCTAssertEqual(errorData["_success"] as? Bool, false) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) + XCTAssertEqual(errorData["_error"] as? String, "Unauthorized()") + + let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) + + XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) + XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) + } + + /// Data is decoded as a call of `revert Unauthorized()`. Decoded only if custom error ABI is given. + /// Trying to decode as `Unauthorized(string)`. Must fail. + func testDecodeRevertWithCustomErrorFailed() { + /// 82b42900 - Unauthorized() function selector + /// 00000000000000000000000000000000000000000000000000000000 - padding bytes + let errorResponse = Data.fromHex("82b4290000000000000000000000000000000000000000000000000000000000")! + let errors: [String: EthError] = ["82b42900" : .init(name: "Unauthorized", inputs: [.init(name: "", type: .string)])] + guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { + XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") + return + } + + XCTAssertEqual(errorData["_success"] as? Bool, false) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) + XCTAssertEqual(errorData["_error"] as? String, "Unauthorized(string)") + XCTAssertEqual(errorData["_parsingError"] as? String, "Data matches Unauthorized(string) but failed to be decoded.") + + let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) + + XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) + XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) + XCTAssertEqual(errorData["_parsingError"] as? String, decodedOutput["_parsingError"] as? String) + } + + /// Data is decoded as a call of `revert Unauthorized("Reason")`. Decoded only if custom error ABI is given. + /// The custom error argument must be extractable by index and name if the name is available. + func testDecodeRevertWithCustomErrorWithArguments() { + /// 973d02cb - `Unauthorized(string)` function selector + /// 0000000000000000000000000000000000000000000000000000000000000020 - data offset + /// 0000000000000000000000000000000000000000000000000000000000000006 - first custom argument length + /// 526561736f6e0000000000000000000000000000000000000000000000000000 - first custom argument bytes + 0 bytes padding + /// 0000... - some more 0 bytes padding to make the number of bytes match 32 bytes chunks + let errorResponse = Data.fromHex("973d02cb00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006526561736f6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")! + let errors: [String: EthError] = ["973d02cb" : .init(name: "Unauthorized", inputs: [.init(name: "message_arg", type: .string)])] + guard let errorData = emptyFunction.decodeErrorResponse(errorResponse, errors: errors) else { + XCTFail("Data must be decoded as a `revert(\"Not enough Ether provided.\")` or `require(false, \"Not enough Ether provided.\")` but decoding failed completely.") + return + } + + XCTAssertEqual(errorData["_success"] as? Bool, false) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, true) + XCTAssertEqual(errorData["_error"] as? String, "Unauthorized(string message_arg)") + XCTAssertEqual(errorData["0"] as? String, "Reason") + XCTAssertEqual(errorData["0"] as? String, errorData["message_arg"] as? String) + + let decodedOutput = oneOutputFunction.decodeReturnData(errorResponse, errors: errors) + + XCTAssertEqual(errorData["_success"] as? Bool, decodedOutput["_success"] as? Bool) + XCTAssertEqual(errorData["_abortedByRevertOrRequire"] as? Bool, decodedOutput["_abortedByRevertOrRequire"] as? Bool) + XCTAssertEqual(errorData["_error"] as? String, decodedOutput["_error"] as? String) + XCTAssertEqual(errorData["0"] as? String, decodedOutput["0"] as? String) + XCTAssertEqual(errorData["message_arg"] as? String, decodedOutput["message_arg"] as? String) + } +} diff --git a/Tests/web3swiftTests/localTests/ABIElementsTests.swift b/Tests/web3swiftTests/localTests/ABIElementsTests.swift deleted file mode 100644 index 4cd1a5eb3..000000000 --- a/Tests/web3swiftTests/localTests/ABIElementsTests.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// ABIElementsTest.swift -// -// Created by JeneaVranceanu on 28.11.2022. -// - -import Foundation -import XCTest -import Core - -class ABIElementsTest: XCTestCase { - typealias EthError = ABI.Element.EthError - - /// Function with any parameters should be able to decode `require` and `revert` calls in soliditiy. - /// Note: `require(expression)` and `revert()` without a message return 0 bytes thus we cannot guarantee - /// that 0 bytes response will be interpreted correctly. - private let emptyFunction = ABI.Element.Function(name: "any", - inputs: [], - outputs: [], - constant: false, - payable: false) - - func testErrorRepresentation() { - XCTAssertEqual(EthError(name: "Error", inputs: []).errorDeclaration, "Error()") - XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "", type: .address)]).errorDeclaration, "Error(address)") - XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address)]).errorDeclaration, "Error(address)") - XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: " ", type: .address), .init(name: "", type: .uint(bits: 256))]).errorDeclaration, "Error(address,uint256)") - XCTAssertEqual(EthError(name: "Error", inputs: [.init(name: "sender", type: .address), .init(name: " ", type: .uint(bits: 256))]).errorDeclaration, "Error(address sender,uint256)") - // Not all types are supported in errors, e.g. tuples and functions are not supported - let allTypesNamedAndNot: [ABI.Element.InOut] = [ - .init(name: "sender", type: .address), - .init(name: "", type: .address), - .init(name: "", type: .uint(bits: 8)), - .init(name: "", type: .uint(bits: 16)), - .init(name: "", type: .uint(bits: 32)), - .init(name: "", type: .uint(bits: 64)), - .init(name: "", type: .uint(bits: 128)), - .init(name: "", type: .uint(bits: 256)), - .init(name: "my_int_8", type: .int(bits: 8)), - .init(name: "my_int_16", type: .int(bits: 16)), - .init(name: "my_int_32", type: .int(bits: 32)), - .init(name: "my_int_64", type: .int(bits: 64)), - .init(name: "my_int_128", type: .int(bits: 128)), - .init(name: "my_int_256", type: .int(bits: 256)), - .init(name: "someFlag", type: .bool), - .init(name: "rand_bytes", type: .bytes(length: 123)), - .init(name: "", type: .dynamicBytes), - .init(name: "arrarrarray123", type: .array(type: .bool, length: 0)), - .init(name: "error_message_maybe", type: .string), - ] - XCTAssertEqual(EthError(name: "VeryCustomErrorName", - inputs: allTypesNamedAndNot).errorDeclaration, - "VeryCustomErrorName(address sender,address,uint8,uint16,uint32,uint64,uint128,uint256,int8 my_int_8,int16 my_int_16,int32 my_int_32,int64 my_int_64,int128 my_int_128,int256 my_int_256,bool someFlag,bytes123 rand_bytes,bytes,bool[] arrarrarray123,string error_message_maybe)") - } - -} From 6a5ce1502b54678140055101ee0fa6704d36eedd Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 6 Dec 2022 00:09:36 +0200 Subject: [PATCH 21/30] fix: return message from Web3Error.valueError --- Sources/Core/Web3Error/Web3Error.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Core/Web3Error/Web3Error.swift b/Sources/Core/Web3Error/Web3Error.swift index 251bcb069..bb21193dc 100644 --- a/Sources/Core/Web3Error/Web3Error.swift +++ b/Sources/Core/Web3Error/Web3Error.swift @@ -54,8 +54,8 @@ public enum Web3Error: LocalizedError { return "Server error: \(code)" case let .clientError(code: code): return "Client error: \(code)" - case .valueError: - return "You're passing value that doesn't supported by this method." + case .valueError(let errorDescription): + return (errorDescription?.isEmpty ?? true) ? "You're passing value that isn't supported by this method" : errorDescription! } } } From b9ac46597a427c97e2ccd41daad1cb54d84e727e Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 6 Dec 2022 00:11:18 +0200 Subject: [PATCH 22/30] fix: removed redundant spacing --- Tests/web3swiftTests/localTests/KeystoresTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/web3swiftTests/localTests/KeystoresTests.swift b/Tests/web3swiftTests/localTests/KeystoresTests.swift index 4911c6c39..3bce14c33 100755 --- a/Tests/web3swiftTests/localTests/KeystoresTests.swift +++ b/Tests/web3swiftTests/localTests/KeystoresTests.swift @@ -16,12 +16,12 @@ class KeystoresTests: LocalTestCase { func testBIP39 () throws { var entropy = Data.fromHex("00000000000000000000000000000000")! var phrase = BIP39.generateMnemonicsFromEntropy(entropy: entropy) - XCTAssert( phrase == "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about") + XCTAssert(phrase == "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about") var seed = BIP39.seedFromMmemonics(phrase!, password: "TREZOR") XCTAssert(seed?.toHexString() == "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04") entropy = Data.fromHex("68a79eaca2324873eacc50cb9c6eca8cc68ea5d936f98787c60c7ebc74e6ce7c")! phrase = BIP39.generateMnemonicsFromEntropy(entropy: entropy) - XCTAssert( phrase == "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length") + XCTAssert(phrase == "hamster diagram private dutch cause delay private meat slide toddler razor book happy fancy gospel tennis maple dilemma loan word shrug inflict delay length") seed = BIP39.seedFromMmemonics(phrase!, password: "TREZOR") XCTAssert(seed?.toHexString() == "64c87cde7e12ecf6704ab95bb1408bef047c22db4cc7491c4271d170a1b213d20b385bc1588d9c7b38f1b39d415665b8a9030c9ec653d75e65f847d8fc1fc440") } From 60c9ca702ca22997121e7241ddcd853e94b75500 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Tue, 6 Dec 2022 18:22:20 +0200 Subject: [PATCH 23/30] feat: ContractProtocol - new function to search for ABI.Element.Function called in a transaction --- Sources/Core/Contract/ContractProtocol.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Sources/Core/Contract/ContractProtocol.swift b/Sources/Core/Contract/ContractProtocol.swift index 495b032f7..9302cbc8e 100755 --- a/Sources/Core/Contract/ContractProtocol.swift +++ b/Sources/Core/Contract/ContractProtocol.swift @@ -173,6 +173,11 @@ public protocol ContractProtocol { /// - Returns: `true` if event is possibly present, `false` if definitely not present and `nil` if event with given name /// is not part of the ``EthereumContract/abi``. func testBloomForEventPresence(eventName: String, bloom: EthereumBloomFilter) -> Bool? + + /// Given the transaction data searches for a match in ``ContractProtocol/methods``. + /// - Parameter data: encoded function call used in transaction data field. Must be at least 4 bytes long. + /// - Returns: function decoded from the ABI of this contract or `nil` if nothing was found. + func getFunctionCalled(_ data: Data) -> ABI.Element.Function? } // MARK: - Overloaded ContractProtocol's functions @@ -333,4 +338,9 @@ extension DefaultContractProtocol { guard let function = methods[methodSignature]?.first else { return nil } return function.decodeInputData(Data(data[4 ..< data.count])) } + + public func getFunctionCalled(_ data: Data) -> ABI.Element.Function? { + guard data.count >= 4 else { return nil } + return methods[data[0..<4].toHexString().addHexPrefix()]?.first + } } From d08a2de3e09c3ab98db0b10aaf2dacaa20d1568c Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 7 Dec 2022 02:05:15 +0200 Subject: [PATCH 24/30] chore: added tests for dynamic types encoding --- .../localTests/ABIEncoderTest.swift | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Tests/web3swiftTests/localTests/ABIEncoderTest.swift b/Tests/web3swiftTests/localTests/ABIEncoderTest.swift index 9e36c9b4d..2e39c6e58 100644 --- a/Tests/web3swiftTests/localTests/ABIEncoderTest.swift +++ b/Tests/web3swiftTests/localTests/ABIEncoderTest.swift @@ -13,7 +13,7 @@ import Core class ABIEncoderTest: XCTestCase { - func test_soliditySha3() throws { + func testSoliditySha3() throws { var hex = try ABIEncoder.soliditySha3(true).toHexString().addHexPrefix() assert(hex == "0x5fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2") hex = try ABIEncoder.soliditySha3(-10).toHexString().addHexPrefix() @@ -66,7 +66,7 @@ class ABIEncoderTest: XCTestCase { assert(hex == "0xa13b31627c1ed7aaded5aecec71baf02fe123797fffd45e662eac8e06fbe4955") } - func test_soliditySha3Fail_FloatDouble() throws { + func testSoliditySha3FailFloatDouble() throws { assert((try? ABIEncoder.soliditySha3(Float(1))) == nil) assert((try? ABIEncoder.soliditySha3(Double(1))) == nil) assert((try? ABIEncoder.soliditySha3(CGFloat(1))) == nil) @@ -78,7 +78,7 @@ class ABIEncoderTest: XCTestCase { /// `[AnyObject]` is not allowed to be used directly as input for `solidtySha3`. /// `AnyObject` erases type data making it impossible to encode some types correctly, /// e.g.: Bool can be treated as Int (8/16/32/64) and 0/1 numbers can be treated as Bool. - func test_soliditySha3Fail_1() throws { + func testSoliditySha3Fail_1() throws { var didFail = false do { _ = try ABIEncoder.soliditySha3([""] as [AnyObject]) @@ -91,7 +91,7 @@ class ABIEncoderTest: XCTestCase { /// `AnyObject` is not allowed to be used directly as input for `solidtySha3`. /// `AnyObject` erases type data making it impossible to encode some types correctly, /// e.g.: Bool can be treated as Int (8/16/32/64) and 0/1 numbers can be treated as Bool. - func test_soliditySha3Fail_2() throws { + func testSoliditySha3Fail_2() throws { var didFail = false do { _ = try ABIEncoder.soliditySha3("" as AnyObject) @@ -101,7 +101,7 @@ class ABIEncoderTest: XCTestCase { XCTAssertTrue(didFail) } - func test_abiEncoding_emptyValues() { + func testAbiEncodingEmptyValues() { let zeroBytes = ABIEncoder.encode(types: [ABI.Element.InOut](), values: [AnyObject]())! XCTAssert(zeroBytes.count == 0) @@ -114,4 +114,24 @@ class ABIEncoderTest: XCTestCase { XCTAssertTrue(functionWithNoInput.methodEncoding == encodedFunction) XCTAssertTrue("0xe16b4a9b" == encodedFunction?.toHexString().addHexPrefix().lowercased()) } + + func testAbiEncodingDynamicTypes() { + var encodedValue = ABIEncoder.encode(types: [.dynamicBytes], values: [Data.fromHex("6761766f66796f726b")!] as [AnyObject])!.toHexString() + XCTAssertEqual(encodedValue, "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096761766f66796f726b0000000000000000000000000000000000000000000000") + + encodedValue = ABIEncoder.encode(types: [.dynamicBytes], values: [Data.fromHex("731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b")!] as [AnyObject])!.toHexString() + XCTAssertEqual(encodedValue, "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b") + + encodedValue = ABIEncoder.encode(types: [.dynamicBytes], values: [Data.fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1")!] as [AnyObject])!.toHexString() + XCTAssertEqual(encodedValue, "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000009ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff100") + + encodedValue = ABIEncoder.encode(types: [.dynamicBytes], values: [Data.fromHex("c3a40000c3a4")!] as [AnyObject])!.toHexString() + XCTAssertEqual(encodedValue, "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006c3a40000c3a40000000000000000000000000000000000000000000000000000") + + encodedValue = ABIEncoder.encode(types: [.string], values: ["gavofyork"] as [AnyObject])!.toHexString() + XCTAssertEqual(encodedValue, "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000096761766f66796f726b0000000000000000000000000000000000000000000000") + + encodedValue = ABIEncoder.encode(types: [.string], values: ["Heeäööä👅D34ɝɣ24Єͽ-.,äü+#/"] as [AnyObject])!.toHexString() + XCTAssertEqual(encodedValue, "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026486565c3a4c3b6c3b6c3a4f09f9185443334c99dc9a33234d084cdbd2d2e2cc3a4c3bc2b232f0000000000000000000000000000000000000000000000000000") + } } From 97ba8e98a8667699c4fa47923de92dd7c8223b79 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 7 Dec 2022 11:14:09 +0200 Subject: [PATCH 25/30] feat: mocking network responses --- .../EthereumAPICalls/Ethereum/Eth+Call.swift | 9 +- Sources/web3swift/Web3/Web3+Instance.swift | 17 ++- Tests/web3swiftTests/localTests/Mocks.swift | 17 +++ .../ST20AndSecurityTokenTests.swift | 143 ++++++++++++++++++ .../ST20AndSecurityTokenTests.swift | 59 -------- 5 files changed, 178 insertions(+), 67 deletions(-) create mode 100644 Tests/web3swiftTests/localTests/Mocks.swift create mode 100644 Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift delete mode 100644 Tests/web3swiftTests/remoteTests/ST20AndSecurityTokenTests.swift diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift index c837c1ea0..caa615497 100755 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift @@ -6,8 +6,13 @@ import Foundation import Core -extension Web3.Eth { - public func callTransaction(_ transaction: CodableTransaction) async throws -> Data { +public protocol IEth { + var provider: Web3Provider { get } + func callTransaction(_ transaction: CodableTransaction) async throws -> Data +} + +public extension IEth { + func callTransaction(_ transaction: CodableTransaction) async throws -> Data { let request = APIRequest.call(transaction, transaction.callOnBlock ?? .latest) return try await APIRequest.sendRequest(with: provider, for: request).result } diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index 1e54f5835..5aa7680c1 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -35,15 +35,20 @@ public class Web3 { } // FIXME: Rewrite this to CodableTransaction - public class Eth { - var provider: Web3Provider - // weak var web3: web3? + public class Eth: IEth { + public var provider: Web3Provider + // FIXME: web3 must be weak var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov web3 = web3instance } + + public func callTransaction(_ transaction: CodableTransaction) async throws -> Data { + let request = APIRequest.call(transaction, transaction.callOnBlock ?? .latest) + return try await APIRequest.sendRequest(with: provider, for: request).result + } } var personalInstance: Web3.Personal? @@ -60,7 +65,7 @@ public class Web3 { // FIXME: Rewrite this to CodableTransaction public class Personal { var provider: Web3Provider - // weak var web3: web3? + // FIXME: web3 must be weak var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov @@ -82,7 +87,7 @@ public class Web3 { // FIXME: Rewrite this to CodableTransaction public class TxPool { var provider: Web3Provider - // weak var web3: web3? + // FIXME: web3 must be weak var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov @@ -103,7 +108,7 @@ public class Web3 { public class Web3Wallet { var provider: Web3Provider - // weak var web3: web3? + // FIXME: web3 must be weak var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov diff --git a/Tests/web3swiftTests/localTests/Mocks.swift b/Tests/web3swiftTests/localTests/Mocks.swift new file mode 100644 index 000000000..e79d56945 --- /dev/null +++ b/Tests/web3swiftTests/localTests/Mocks.swift @@ -0,0 +1,17 @@ +// +// Mocks.swift +// +// Created by JeneaVranceanu on 07.12.2022. +// + +import Foundation +@testable import web3swift +@testable import Core + +class Web3EthMock: Web3.Eth { + var onCallTransaction: ((CodableTransaction) -> Data)? + + override func callTransaction(_ transaction: CodableTransaction) async throws -> Data { + onCallTransaction?(transaction) ?? Data() + } +} diff --git a/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift b/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift new file mode 100644 index 000000000..7851d6853 --- /dev/null +++ b/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift @@ -0,0 +1,143 @@ +// +// web3swift_ST20_Tests.swift +// web3swift-iOS_Tests +// +// Created by Anton on 15/03/2019. +// Copyright © 2019 The Matter Inc. All rights reserved. +// +import XCTest +import BigInt +import web3swift +import Core + +@testable import web3swift + +class ST20AndSecurityTokenTests: XCTestCase { + + var web3: Web3! + var ethMock: Web3EthMock! + var st20token: ST20! + var securityToken: SecurityToken! + + override func setUp() async throws { + web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken) + ethMock = Web3EthMock(provider: web3.provider, web3: web3) + web3.ethInstance = ethMock + st20token = ST20.init(web3: web3, provider: web3.provider, address: .contractDeploymentAddress()) + securityToken = SecurityToken.init(web3: web3, provider: web3.provider, address: .contractDeploymentAddress()) + } + + func testST20TokenPropertiesBasedOnERC20() async throws { + let expectedSymbol = "RandomTokenSymbol953" + let expectedName = "WhatA NAME - l953. Never seen again!" + let expectedDecimals = UInt8.random(in: 0...255) + + ethMock.onCallTransaction = { transaction in + guard let function = self.st20token.contract.contract.getFunctionCalled(transaction.data) else { + XCTFail("Failed to decode function call to determine what shall be returned") + return Data() + } + switch function.name { + case "symbol": + return ABIEncoder.encode(types: [.string], values: [expectedSymbol] as [AnyObject])! + case "name": + return ABIEncoder.encode(types: [.string], values: [expectedName] as [AnyObject])! + case "decimals": + return ABIEncoder.encode(types: [.uint(bits: 8)], values: [expectedDecimals] as [AnyObject])! + default: + // Unexpected function called + XCTFail("Called function '\(String(describing: function.name))' which wasn't supposed to be called.") + return Data() + } + } + + try await st20token.readProperties() + XCTAssertEqual(st20token.symbol, expectedSymbol) + XCTAssertEqual(st20token.name, expectedName) + XCTAssertEqual(st20token.decimals, expectedDecimals) + } + + func testST20TokenBalanceAndAllowance() async throws { + let expectedAllowance = BigUInt.randomInteger(lessThan: BigUInt(10000000000)) + let expectedBalance = BigUInt.randomInteger(lessThan: BigUInt(10000000000)) + + let userAddress = EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")! + let delegate = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! + + ethMock.onCallTransaction = { transaction in + guard let function = self.st20token.contract.contract.getFunctionCalled(transaction.data) else { + XCTFail("Failed to decode function call to determine what shall be returned") + return Data() + } + switch function.name { + case "balanceOf": + let address = function.decodeInputData(transaction.data)?["0"] as? EthereumAddress + XCTAssertEqual(address, userAddress) + return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedBalance] as [AnyObject])! + case "allowance": + let transactionInput = function.decodeInputData(transaction.data) + XCTAssertEqual(transactionInput?["0"] as? EthereumAddress, userAddress) + XCTAssertEqual(transactionInput?["1"] as? EthereumAddress, delegate) + return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedAllowance] as [AnyObject])! + default: + // Unexpected function called + XCTFail("Called function '\(String(describing: function.name))' which wasn't supposed to be called.") + return Data() + } + } + + let balance = try await st20token.getBalance(account: userAddress) + let allowance = try await st20token.getAllowance(originalOwner: userAddress, delegate: delegate) + XCTAssertEqual(balance, expectedBalance) + XCTAssertEqual(allowance, expectedAllowance) + } + + func testSecurityTokenInvestors() async throws { + let expectedNumberOfInvestors = BigUInt.randomInteger(lessThan: BigUInt(10000000000)) +// let expectedInvestors = [EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")!,EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")!] + + ethMock.onCallTransaction = { transaction in + guard let function = self.securityToken.contract.contract.getFunctionCalled(transaction.data) else { + XCTFail("Failed to decode function call to determine what shall be returned") + return Data() + } + switch function.name { + case "investorCount": + return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedNumberOfInvestors] as [AnyObject])! +// case "investors": +// return ABIEncoder.encode(types: [.array(type: .address, length: 0)], +// values: [expectedInvestors] as [AnyObject])! + default: + // Unexpected function called + XCTFail("Called function '\(String(describing: function.name))' which wasn't supposed to be called.") + return Data() + } + } + + let investorsCount = try await securityToken.investorCount() + XCTAssertEqual(investorsCount, expectedNumberOfInvestors) +// XCTAssertEqual(investors, expectedInvestors) + } + + func testSecurityTokenGranularity() async throws { + let expectedGranularity = BigUInt.randomInteger(lessThan: BigUInt(10000000000)) + + ethMock.onCallTransaction = { transaction in + guard let function = self.securityToken.contract.contract.getFunctionCalled(transaction.data) else { + XCTFail("Failed to decode function call to determine what shall be returned") + return Data() + } + switch function.name { + case "granularity": + return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedGranularity] as [AnyObject])! + default: + // Unexpected function called + XCTFail("Called function '\(String(describing: function.name))' which wasn't supposed to be called.") + return Data() + } + } + + let granularity = try await securityToken.getGranularity() + XCTAssertEqual(granularity, expectedGranularity) + } +} diff --git a/Tests/web3swiftTests/remoteTests/ST20AndSecurityTokenTests.swift b/Tests/web3swiftTests/remoteTests/ST20AndSecurityTokenTests.swift deleted file mode 100644 index be8d5b234..000000000 --- a/Tests/web3swiftTests/remoteTests/ST20AndSecurityTokenTests.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// web3swift_ST20_Tests.swift -// web3swift-iOS_Tests -// -// Created by Anton on 15/03/2019. -// Copyright © 2019 The Matter Inc. All rights reserved. -// -import XCTest -import BigInt -import web3swift -import Core - -@testable import web3swift - -// MARK: Works only with network connection -class ST20AndSecurityTokenTests: XCTestCase { - - // FIXME: Enable me back again - // Test fails because there's no such wallet on goerli chain as well as token. -// func testERC20TokenCreation() async throws { -// let web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken) -// let w3sTokenAddress = EthereumAddress("0x33d191db2486e0d245b44fde3fae5ed667d5694b")! -// let st20token = ST20.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) -// try await st20token.readProperties() -// XCTAssertEqual(st20token.symbol(), "MIMI") -// XCTAssertEqual(st20token.name(), "Mimi") -// XCTAssertEqual(st20token.decimals(), 18) -// } - - func testST20tokenBalanceAndAllowance() async throws { - let web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken) - let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! - let st20token = ST20.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) - let userAddress = EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")! - let balance = try await st20token.getBalance(account: userAddress) - let allowance = try await st20token.getAllowance(originalOwner: userAddress, delegate: userAddress) - XCTAssertEqual(balance, 0) - XCTAssertEqual(allowance, 0) - } - - func testSecurityTokenInvestors() async throws { - let web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken) - let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! - let stoken = SecurityToken.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) - let investorsCount = try await stoken.investorCount() - XCTAssertEqual(investorsCount, 0) - } - - // FIXME: Enable me back again - // Test fails because there's no such wallet on goerli chain as well as token. -// func testSecurityTokenGranularity() async throws { -// let web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken) -// let w3sTokenAddress = EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")! -// let stoken = SecurityToken.init(web3: web3, provider: web3.provider, address: w3sTokenAddress) -// let granularity = try await stoken.getGranularity() -// let stringGranularity = String(granularity) -// XCTAssertEqual(stringGranularity, "1000000000000000000") -// } -} From ff0778b59fc7839eb96cf6732b070b2e40f3b275 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 7 Dec 2022 15:09:28 +0200 Subject: [PATCH 26/30] chore: Web3.Eth + IEth refactoring --- .../EthereumAPICalls/Ethereum/Eth+Call.swift | 19 --- .../Ethereum/Eth+EstimateGas.swift | 15 -- .../Ethereum/Eth+FeeHistory.swift | 15 -- .../Ethereum/Eth+GetAccounts.swift | 17 -- .../Ethereum/Eth+GetBalance.swift | 15 -- .../Ethereum/Eth+GetBlockByHash.swift | 15 -- .../Ethereum/Eth+GetBlockByNumber.swift | 20 --- .../Ethereum/Eth+GetBlockNumber.swift | 14 -- .../Ethereum/Eth+GetCode.swift | 15 -- .../Ethereum/Eth+GetGasPrice.swift | 14 -- .../Ethereum/Eth+GetTransactionCount.swift | 15 -- .../Ethereum/Eth+GetTransactionDetails.swift | 15 -- .../Ethereum/Eth+GetTransactionReceipt.swift | 15 -- .../Ethereum/Eth+SendRawTransaction.swift | 28 ---- .../Ethereum/Eth+SendTransaction.swift | 17 -- .../Ethereum/IEth+Defaults.swift | 158 ++++++++++++++++++ .../EthereumAPICalls/Ethereum/IEth.swift | 34 ++++ Tests/web3swiftTests/localTests/Mocks.swift | 4 +- .../ST20AndSecurityTokenTests.swift | 10 +- 19 files changed, 198 insertions(+), 257 deletions(-) delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+EstimateGas.swift delete mode 100644 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+FeeHistory.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetAccounts.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBalance.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByHash.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByNumber.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockNumber.swift delete mode 100644 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetCode.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetGasPrice.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionCount.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionDetails.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionReceipt.swift delete mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendRawTransaction.swift delete mode 100644 Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendTransaction.swift create mode 100644 Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift create mode 100755 Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift deleted file mode 100755 index caa615497..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+Call.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import Core - -public protocol IEth { - var provider: Web3Provider { get } - func callTransaction(_ transaction: CodableTransaction) async throws -> Data -} - -public extension IEth { - func callTransaction(_ transaction: CodableTransaction) async throws -> Data { - let request = APIRequest.call(transaction, transaction.callOnBlock ?? .latest) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+EstimateGas.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+EstimateGas.swift deleted file mode 100755 index 7d1fa7578..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+EstimateGas.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func estimateGas(for transaction: CodableTransaction, onBlock: BlockNumber = .latest) async throws -> BigUInt { - let request = APIRequest.estimateGas(transaction, onBlock) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+FeeHistory.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+FeeHistory.swift deleted file mode 100644 index d290140fc..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+FeeHistory.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - func feeHistory(blockCount: BigUInt, block: BlockNumber, percentiles: [Double]) async throws -> Oracle.FeeHistory { - let request = APIRequest.feeHistory(blockCount, block, percentiles) - return try await APIRequest.sendRequest(with: web3.provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetAccounts.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetAccounts.swift deleted file mode 100755 index f433fabe2..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetAccounts.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func ownedAccounts() async throws -> [EthereumAddress] { - guard web3.provider.attachedKeystoreManager == nil else { - return try web3.wallet.getAccounts() - } - return try await APIRequest.sendRequest(with: web3.provider, for: .getAccounts).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBalance.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBalance.swift deleted file mode 100755 index fb189e2fa..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBalance.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import Core -import BigInt - -extension Web3.Eth { - public func getBalance(for address: EthereumAddress, onBlock: BlockNumber = .latest) async throws -> BigUInt { - let request = APIRequest.getBalance(address.address, onBlock) - return try await APIRequest.sendRequest(with: web3.provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByHash.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByHash.swift deleted file mode 100755 index ad68b740c..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByHash.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func block(by hash: Data, fullTransactions: Bool = false) async throws -> Block { - let request: APIRequest = .getBlockByHash(hash.toHexString().addHexPrefix(), fullTransactions) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByNumber.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByNumber.swift deleted file mode 100755 index 6763df2cc..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockByNumber.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func block(by hash: Hash, fullTransactions: Bool = false) async throws -> Block { - let request = APIRequest.getBlockByHash(hash, fullTransactions) - return try await APIRequest.sendRequest(with: provider, for: request).result - } - - public func block(by number: BlockNumber, fullTransactions: Bool = false) async throws -> Block { - let request = APIRequest.getBlockByNumber(number, fullTransactions) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockNumber.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockNumber.swift deleted file mode 100755 index b63556897..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetBlockNumber.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func blockNumber() async throws -> BigUInt { - try await APIRequest.sendRequest(with: web3.provider, for: .blockNumber).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetCode.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetCode.swift deleted file mode 100644 index f17eda612..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetCode.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import Core -import BigInt - -extension Web3.Eth { - public func code(for address: EthereumAddress, onBlock: BlockNumber = .latest) async throws -> Hash { - let request = APIRequest.getCode(address.address, onBlock) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetGasPrice.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetGasPrice.swift deleted file mode 100755 index e902e7736..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetGasPrice.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func gasPrice() async throws -> BigUInt { - try await APIRequest.sendRequest(with: self.provider, for: .gasPrice).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionCount.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionCount.swift deleted file mode 100755 index 59ff77d35..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionCount.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func getTransactionCount(for address: EthereumAddress, onBlock: BlockNumber = .latest) async throws -> BigUInt { - let request = APIRequest.getTransactionCount(address.address, onBlock) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionDetails.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionDetails.swift deleted file mode 100755 index 34a30a98c..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionDetails.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func transactionDetails(_ txHash: Data) async throws -> TransactionDetails { - let request = APIRequest.getTransactionByHash(txHash.toHexString().addHexPrefix()) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionReceipt.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionReceipt.swift deleted file mode 100755 index a960c5791..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+GetTransactionReceipt.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func transactionReceipt(_ txHash: Data) async throws -> TransactionReceipt { - let request = APIRequest.getTransactionReceipt(txHash.toHexString().addHexPrefix()) - return try await APIRequest.sendRequest(with: provider, for: request).result - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendRawTransaction.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendRawTransaction.swift deleted file mode 100755 index a74bed92f..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendRawTransaction.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Created by Yaroslav Yashin. -// Copyright © 2022 Yaroslav Yashin. All rights reserved. -// - -import Foundation -import Core - -extension Web3.Eth { - public func send(raw data: Data) async throws -> TransactionSendingResult { - let request = APIRequest.sendRawTransaction(data.toHexString().addHexPrefix()) - let response: APIResponse = try await APIRequest.sendRequest(with: provider, for: request) - return try TransactionSendingResult(data: data, hash: response.result) - } -} - -public struct TransactionSendingResult { - public var transaction: CodableTransaction - public var hash: String -} - -fileprivate extension TransactionSendingResult { - init(data: Data, hash: Hash) throws { - guard let transaction = CodableTransaction(rawValue: data) else { throw Web3Error.dataError } - self.transaction = transaction - self.hash = hash - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendTransaction.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendTransaction.swift deleted file mode 100644 index 51f9fe86a..000000000 --- a/Sources/web3swift/EthereumAPICalls/Ethereum/Eth+SendTransaction.swift +++ /dev/null @@ -1,17 +0,0 @@ -// web3swift -// -// Created by Alex Vlasov. -// Copyright © 2018 Alex Vlasov. All rights reserved. -// - -import Foundation -import BigInt -import Core - -extension Web3.Eth { - public func send(_ transaction: CodableTransaction) async throws -> TransactionSendingResult { - let request = APIRequest.sendTransaction(transaction) - let response: APIResponse = try await APIRequest.sendRequest(with: provider, for: request) - return TransactionSendingResult(transaction: transaction, hash: response.result) - } -} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift new file mode 100644 index 000000000..3dcade8c0 --- /dev/null +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth+Defaults.swift @@ -0,0 +1,158 @@ +// +// IEth+Defaults.swift +// +// Created by JeneaVranceanu on 07.12.2022. +// + +import Foundation +import BigInt +import Core + +public extension IEth { + func callTransaction(_ transaction: CodableTransaction) async throws -> Data { + let request = APIRequest.call(transaction, transaction.callOnBlock ?? .latest) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func estimateGas(for transaction: CodableTransaction) async throws -> BigUInt { + try await estimateGas(for: transaction, onBlock: .latest) + } + + func estimateGas(for transaction: CodableTransaction, onBlock: BlockNumber) async throws -> BigUInt { + let request = APIRequest.estimateGas(transaction, onBlock) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func transactionReceipt(_ txHash: Data) async throws -> TransactionReceipt { + let request = APIRequest.getTransactionReceipt(txHash.toHexString().addHexPrefix()) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func transactionDetails(_ txHash: Data) async throws -> TransactionDetails { + let request = APIRequest.getTransactionByHash(txHash.toHexString().addHexPrefix()) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func getTransactionCount(for address: EthereumAddress) async throws -> BigUInt { + try await getTransactionCount(for: address, onBlock: .latest) + } + + func getTransactionCount(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt { + let request = APIRequest.getTransactionCount(address.address, onBlock) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func gasPrice() async throws -> BigUInt { + try await APIRequest.sendRequest(with: self.provider, for: .gasPrice).result + } +} + +public extension IEth { + func code(for address: EthereumAddress) async throws -> Hash { + try await code(for: address, onBlock: .latest) + } + + func code(for address: EthereumAddress, onBlock: BlockNumber) async throws -> Hash { + let request = APIRequest.getCode(address.address, onBlock) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func blockNumber() async throws -> BigUInt { + try await APIRequest.sendRequest(with: provider, for: .blockNumber).result + } +} + +public extension IEth { + func block(by hash: Hash) async throws -> Block { + try await block(by: hash, fullTransactions: false) + } + + func block(by hash: Hash, fullTransactions: Bool) async throws -> Block { + let request = APIRequest.getBlockByHash(hash, fullTransactions) + return try await APIRequest.sendRequest(with: provider, for: request).result + } + + func block(by number: BlockNumber) async throws -> Block { + try await block(by: number, fullTransactions: false) + } + + func block(by number: BlockNumber, fullTransactions: Bool) async throws -> Block { + let request = APIRequest.getBlockByNumber(number, fullTransactions) + return try await APIRequest.sendRequest(with: provider, for: request).result + } + + func block(by hash: Data) async throws -> Block { + try await block(by: hash, fullTransactions: false) + } + + func block(by hash: Data, fullTransactions: Bool) async throws -> Block { + let request: APIRequest = .getBlockByHash(hash.toHexString().addHexPrefix(), fullTransactions) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func getBalance(for address: EthereumAddress) async throws -> BigUInt { + try await getBalance(for: address, onBlock: .latest) + } + + func getBalance(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt { + let request = APIRequest.getBalance(address.address, onBlock) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func ownedAccounts() async throws -> [EthereumAddress] { + if let addresses = provider.attachedKeystoreManager?.addresses { + return addresses + } + return try await APIRequest.sendRequest(with: provider, for: .getAccounts).result + } +} + +public extension IEth { + func feeHistory(blockCount: BigUInt, block: BlockNumber, percentiles: [Double]) async throws -> Oracle.FeeHistory { + let request = APIRequest.feeHistory(blockCount, block, percentiles) + return try await APIRequest.sendRequest(with: provider, for: request).result + } +} + +public extension IEth { + func send(_ transaction: CodableTransaction) async throws -> TransactionSendingResult { + let request = APIRequest.sendTransaction(transaction) + let response: APIResponse = try await APIRequest.sendRequest(with: provider, for: request) + return TransactionSendingResult(transaction: transaction, hash: response.result) + } + + func send(raw data: Data) async throws -> TransactionSendingResult { + guard let transaction = CodableTransaction(rawValue: data) else { + // FIXME: When the PR is merged add this description to dataError -> + // Description to add: + // Link to PR: + throw Web3Error.dataError + } + let request = APIRequest.sendRawTransaction(data.toHexString().addHexPrefix()) + let response: APIResponse = try await APIRequest.sendRequest(with: provider, for: request) + return TransactionSendingResult(transaction: transaction, hash: response.result) + } +} + +// MARK: - Supporting models and extensions + +public struct TransactionSendingResult { + public var transaction: CodableTransaction + public var hash: String +} diff --git a/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift new file mode 100755 index 000000000..69bbc3e5a --- /dev/null +++ b/Sources/web3swift/EthereumAPICalls/Ethereum/IEth.swift @@ -0,0 +1,34 @@ +// +// Created by Yaroslav Yashin. +// Copyright © 2022 Yaroslav Yashin. All rights reserved. +// + +import Foundation +import BigInt +import Core + +public protocol IEth { + var provider: Web3Provider { get } + func callTransaction(_ transaction: CodableTransaction) async throws -> Data + func send(_ transaction: CodableTransaction) async throws -> TransactionSendingResult + func send(raw data: Data) async throws -> TransactionSendingResult + + func estimateGas(for transaction: CodableTransaction, onBlock: BlockNumber) async throws -> BigUInt + func feeHistory(blockCount: BigUInt, block: BlockNumber, percentiles: [Double]) async throws -> Oracle.FeeHistory + func ownedAccounts() async throws -> [EthereumAddress] + func getBalance(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt + + func block(by hash: Data, fullTransactions: Bool) async throws -> Block + func block(by number: BlockNumber, fullTransactions: Bool) async throws -> Block + func block(by hash: Hash, fullTransactions: Bool) async throws -> Block + func blockNumber() async throws -> BigUInt + + func code(for address: EthereumAddress, onBlock: BlockNumber) async throws -> Hash + + func gasPrice() async throws -> BigUInt + + func getTransactionCount(for address: EthereumAddress, onBlock: BlockNumber) async throws -> BigUInt + + func transactionDetails(_ txHash: Data) async throws -> TransactionDetails + func transactionReceipt(_ txHash: Data) async throws -> TransactionReceipt +} diff --git a/Tests/web3swiftTests/localTests/Mocks.swift b/Tests/web3swiftTests/localTests/Mocks.swift index e79d56945..9b7ec6483 100644 --- a/Tests/web3swiftTests/localTests/Mocks.swift +++ b/Tests/web3swiftTests/localTests/Mocks.swift @@ -8,10 +8,10 @@ import Foundation @testable import web3swift @testable import Core -class Web3EthMock: Web3.Eth { +class Web3EthMock: IEth { var onCallTransaction: ((CodableTransaction) -> Data)? - override func callTransaction(_ transaction: CodableTransaction) async throws -> Data { + func callTransaction(_ transaction: CodableTransaction) async throws -> Data { onCallTransaction?(transaction) ?? Data() } } diff --git a/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift b/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift index 7851d6853..ce8df1682 100644 --- a/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift +++ b/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift @@ -1,13 +1,11 @@ // -// web3swift_ST20_Tests.swift -// web3swift-iOS_Tests +// ST20AndSecurityTokenTests.swift // -// Created by Anton on 15/03/2019. -// Copyright © 2019 The Matter Inc. All rights reserved. +// Created by JeneaVranceanu on 07.12.2022. // + import XCTest import BigInt -import web3swift import Core @testable import web3swift @@ -21,7 +19,7 @@ class ST20AndSecurityTokenTests: XCTestCase { override func setUp() async throws { web3 = await Web3.InfuraGoerliWeb3(accessToken: Constants.infuraToken) - ethMock = Web3EthMock(provider: web3.provider, web3: web3) + ethMock = Web3EthMock(provider: web3.provider) web3.ethInstance = ethMock st20token = ST20.init(web3: web3, provider: web3.provider, address: .contractDeploymentAddress()) securityToken = SecurityToken.init(web3: web3, provider: web3.provider, address: .contractDeploymentAddress()) From 29fff74140162d8b0641e7faf7f229bf64e4a4fa Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 7 Dec 2022 15:11:04 +0200 Subject: [PATCH 27/30] fix: added initializer for the Web3EthMock --- Tests/web3swiftTests/localTests/Mocks.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Tests/web3swiftTests/localTests/Mocks.swift b/Tests/web3swiftTests/localTests/Mocks.swift index 9b7ec6483..de284dd2b 100644 --- a/Tests/web3swiftTests/localTests/Mocks.swift +++ b/Tests/web3swiftTests/localTests/Mocks.swift @@ -9,8 +9,14 @@ import Foundation @testable import Core class Web3EthMock: IEth { + let provider: Web3Provider + var onCallTransaction: ((CodableTransaction) -> Data)? + init(provider: Web3Provider) { + self.provider = provider + } + func callTransaction(_ transaction: CodableTransaction) async throws -> Data { onCallTransaction?(transaction) ?? Data() } From e2b0f5dff01391c3428505fe165431d96207816a Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Wed, 7 Dec 2022 15:11:55 +0200 Subject: [PATCH 28/30] fix: changed type of web3.eth to IEth and removed it's dependency on web3 --- Sources/web3swift/Web3/Web3+Instance.swift | 24 ++++++++-------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index 5aa7680c1..8b07feef4 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -20,34 +20,26 @@ public class Web3 { /// Keystore manager can be bound to Web3 instance. If some manager is bound all further account related functions, such /// as account listing, transaction signing, etc. are done locally using private keys and accounts found in a manager. public func addKeystoreManager(_ manager: KeystoreManager?) { - self.provider.attachedKeystoreManager = manager + provider.attachedKeystoreManager = manager } - var ethInstance: Web3.Eth? + var ethInstance: IEth? /// Public web3.eth.* namespace. - public var eth: Web3.Eth { - if self.ethInstance != nil { - return self.ethInstance! + public var eth: IEth { + if ethInstance != nil { + return ethInstance! } - self.ethInstance = Web3.Eth(provider: self.provider, web3: self) - return self.ethInstance! + ethInstance = Web3.Eth(provider: provider) + return ethInstance! } // FIXME: Rewrite this to CodableTransaction public class Eth: IEth { public var provider: Web3Provider - // FIXME: web3 must be weak - var web3: Web3 - public init(provider prov: Web3Provider, web3 web3instance: Web3) { + public init(provider prov: Web3Provider) { provider = prov - web3 = web3instance - } - - public func callTransaction(_ transaction: CodableTransaction) async throws -> Data { - let request = APIRequest.call(transaction, transaction.callOnBlock ?? .latest) - return try await APIRequest.sendRequest(with: provider, for: request).result } } From bd2188a21fc9cae7a38355b7c074706555907f91 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 8 Dec 2022 18:11:16 +0200 Subject: [PATCH 29/30] chore: removed commented out case from SecurityToken test --- .../localTests/ST20AndSecurityTokenTests.swift | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift b/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift index ce8df1682..738648e7e 100644 --- a/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift +++ b/Tests/web3swiftTests/localTests/ST20AndSecurityTokenTests.swift @@ -92,29 +92,21 @@ class ST20AndSecurityTokenTests: XCTestCase { func testSecurityTokenInvestors() async throws { let expectedNumberOfInvestors = BigUInt.randomInteger(lessThan: BigUInt(10000000000)) -// let expectedInvestors = [EthereumAddress("0xe22b8979739D724343bd002F9f432F5990879901")!,EthereumAddress("0x2dD33957C90880bE4Ee9fd5F703110BDA2E579EC")!] - ethMock.onCallTransaction = { transaction in guard let function = self.securityToken.contract.contract.getFunctionCalled(transaction.data) else { XCTFail("Failed to decode function call to determine what shall be returned") return Data() } - switch function.name { - case "investorCount": + if function.name == "investorCount" { return ABIEncoder.encode(types: [.uint(bits: 256)], values: [expectedNumberOfInvestors] as [AnyObject])! -// case "investors": -// return ABIEncoder.encode(types: [.array(type: .address, length: 0)], -// values: [expectedInvestors] as [AnyObject])! - default: - // Unexpected function called - XCTFail("Called function '\(String(describing: function.name))' which wasn't supposed to be called.") - return Data() } + // Unexpected function called + XCTFail("Called function '\(String(describing: function.name))' which wasn't supposed to be called.") + return Data() } let investorsCount = try await securityToken.investorCount() XCTAssertEqual(investorsCount, expectedNumberOfInvestors) -// XCTAssertEqual(investors, expectedInvestors) } func testSecurityTokenGranularity() async throws { From 3707ca499a5e95a24b2b38cf3b223ac042dbd619 Mon Sep 17 00:00:00 2001 From: Jenea Vranceanu Date: Thu, 8 Dec 2022 18:13:46 +0200 Subject: [PATCH 30/30] chore: updated FIXME comments to "// FIXME: remove dependency on web3 instance!!" --- Sources/web3swift/Web3/Web3+Instance.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/web3swift/Web3/Web3+Instance.swift b/Sources/web3swift/Web3/Web3+Instance.swift index 8b07feef4..a9b439a56 100755 --- a/Sources/web3swift/Web3/Web3+Instance.swift +++ b/Sources/web3swift/Web3/Web3+Instance.swift @@ -57,7 +57,7 @@ public class Web3 { // FIXME: Rewrite this to CodableTransaction public class Personal { var provider: Web3Provider - // FIXME: web3 must be weak + // FIXME: remove dependency on web3 instance!! var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov @@ -79,7 +79,7 @@ public class Web3 { // FIXME: Rewrite this to CodableTransaction public class TxPool { var provider: Web3Provider - // FIXME: web3 must be weak + // FIXME: remove dependency on web3 instance!! var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov @@ -100,7 +100,7 @@ public class Web3 { public class Web3Wallet { var provider: Web3Provider - // FIXME: web3 must be weak + // FIXME: remove dependency on web3 instance!! var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov @@ -122,7 +122,7 @@ public class Web3 { // FIXME: Rewrite this to CodableTransaction public class BrowserFunctions { var provider: Web3Provider - // weak var web3: web3? + // FIXME: remove dependency on web3 instance!! public var web3: Web3 public init(provider prov: Web3Provider, web3 web3instance: Web3) { provider = prov @@ -153,7 +153,7 @@ public class Web3 { } var provider: Web3Provider - // weak var web3: web3? + // FIXME: remove dependency on web3 instance!! var web3: Web3 var timer: RepeatingTimer?