From d82fdd7b9d21b771ad99befe6e188cf6db87e9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 2 Feb 2022 15:52:27 -0300 Subject: [PATCH 01/16] Add account struct --- Sources/WalletConnect/Account.swift | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Sources/WalletConnect/Account.swift diff --git a/Sources/WalletConnect/Account.swift b/Sources/WalletConnect/Account.swift new file mode 100644 index 000000000..b498ec45a --- /dev/null +++ b/Sources/WalletConnect/Account.swift @@ -0,0 +1,42 @@ +/** + + */ +public struct Account { + + /// + public let namespace: String + + /// + public let reference: String + + /// + public let address: String + + /// + public var chainIdentifier: String { + "\(namespace):\(reference)" + } + + /// + public var absoluteString: String { + "\(namespace):\(reference):\(address)" + } + + public var isCAIP10Conformant: Bool { + return String.conformsToCAIP10(absoluteString) + } + + public init(chainNamespace: String, chainReference: String, address: String) { + self.namespace = chainNamespace + self.reference = chainReference + self.address = address + } + + public init?(string: String) { + guard String.conformsToCAIP10(string) else { return nil } + let splits = string.split(separator: ":") + self.namespace = String(splits[0]) + self.reference = String(splits[1]) + self.address = String(splits[2]) + } +} From 47ac3ae5b43bb72e294e10fc3c72b5723f89b429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 2 Feb 2022 16:04:12 -0300 Subject: [PATCH 02/16] Account type unit tests --- Sources/WalletConnect/Account.swift | 12 +-------- Tests/WalletConnectTests/AccountTests.swift | 27 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 Tests/WalletConnectTests/AccountTests.swift diff --git a/Sources/WalletConnect/Account.swift b/Sources/WalletConnect/Account.swift index b498ec45a..80f1ee8e2 100644 --- a/Sources/WalletConnect/Account.swift +++ b/Sources/WalletConnect/Account.swift @@ -13,7 +13,7 @@ public struct Account { public let address: String /// - public var chainIdentifier: String { + public var blockchainIdentifier: String { "\(namespace):\(reference)" } @@ -22,16 +22,6 @@ public struct Account { "\(namespace):\(reference):\(address)" } - public var isCAIP10Conformant: Bool { - return String.conformsToCAIP10(absoluteString) - } - - public init(chainNamespace: String, chainReference: String, address: String) { - self.namespace = chainNamespace - self.reference = chainReference - self.address = address - } - public init?(string: String) { guard String.conformsToCAIP10(string) else { return nil } let splits = string.split(separator: ":") diff --git a/Tests/WalletConnectTests/AccountTests.swift b/Tests/WalletConnectTests/AccountTests.swift new file mode 100644 index 000000000..7181899ad --- /dev/null +++ b/Tests/WalletConnectTests/AccountTests.swift @@ -0,0 +1,27 @@ +import XCTest +@testable import WalletConnect + +final class AccountTests: XCTestCase { + + func testInit() { + // Valid accounts + XCTAssertNotNil(Account(string: "std:0:0")) + XCTAssertNotNil(Account(string: "chainstd:8c3444cf8970a9e41a706fab93e7a6c4:6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7")) + + // Invalid accounts + XCTAssertNil(Account(string: "std:0:$")) + XCTAssertNil(Account(string: "std:$:0")) + XCTAssertNil(Account(string: "st:0:0")) + } + + func testBlockchainIdentifier() { + let account = Account(string: "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb") + XCTAssertEqual(account?.blockchainIdentifier, "eip155:1") + } + + func testAbsoluteString() { + let accountString = "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb" + let account = Account(string: accountString) + XCTAssertEqual(account?.absoluteString, accountString) + } +} From 877482ad7016a159133d2685031599d9a90259e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 2 Feb 2022 17:20:37 -0300 Subject: [PATCH 03/16] Account type documentation --- Sources/WalletConnect/Account.swift | 36 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/Sources/WalletConnect/Account.swift b/Sources/WalletConnect/Account.swift index 80f1ee8e2..3e37d1e41 100644 --- a/Sources/WalletConnect/Account.swift +++ b/Sources/WalletConnect/Account.swift @@ -1,27 +1,47 @@ /** + A value that identifies an account in any given blockchain. + This structure parses account IDs according to [CAIP-10]. + Account IDs are prefixed with a [CAIP-2] blockchain ID, delimited by a `':'` character, followed by the account address. + + Specifying a blockchain account by using a chain-agnostic identifier is useful to allow interoperability between multiple + chains when using both wallets and decentralized applications. + + [CAIP-2]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md + [CAIP-10]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md */ public struct Account { - /// - public let namespace: String + /// A blockchain namespace. Usually describes an ecosystem or standard. + public var namespace: String - /// - public let reference: String + /// A reference string that identifies a blockchain within a given namespace. + public var reference: String - /// - public let address: String + /// The account's address specific to the blockchain. + public var address: String - /// + /// The CAIP-2 blockchain identifier of the account. public var blockchainIdentifier: String { "\(namespace):\(reference)" } - /// + /// The CAIP-10 account identifier absolute string. public var absoluteString: String { "\(namespace):\(reference):\(address)" } + /// Returns whether the account conforms to CAIP-10. + public var isCAIP10Conformant: Bool { + String.conformsToCAIP10(absoluteString) + } + + /** + Creates an account instance from the provided string. + + This initializer returns nil if the string doesn't represent a valid account id in conformance with + [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md). + */ public init?(string: String) { guard String.conformsToCAIP10(string) else { return nil } let splits = string.split(separator: ":") From 8545daff75d6e4a773ab5a12b6870100269ab051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 2 Feb 2022 19:06:33 -0300 Subject: [PATCH 04/16] Add conformance to utility protocols --- Sources/WalletConnect/Account.swift | 27 +++++++++++++++++++-- Tests/WalletConnectTests/AccountTests.swift | 25 ++++++++++++------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/Sources/WalletConnect/Account.swift b/Sources/WalletConnect/Account.swift index 3e37d1e41..c83865116 100644 --- a/Sources/WalletConnect/Account.swift +++ b/Sources/WalletConnect/Account.swift @@ -10,7 +10,7 @@ [CAIP-2]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md [CAIP-10]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md */ -public struct Account { +public struct Account: Equatable { /// A blockchain namespace. Usually describes an ecosystem or standard. public var namespace: String @@ -42,7 +42,7 @@ public struct Account { This initializer returns nil if the string doesn't represent a valid account id in conformance with [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md). */ - public init?(string: String) { + public init?(_ string: String) { guard String.conformsToCAIP10(string) else { return nil } let splits = string.split(separator: ":") self.namespace = String(splits[0]) @@ -50,3 +50,26 @@ public struct Account { self.address = String(splits[2]) } } + +extension Account: LosslessStringConvertible { + public var description: String { + return absoluteString + } +} + +extension Account: Codable { + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let absoluteString = try container.decode(String.self) + guard let account = Account(absoluteString) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Malformed CAIP-10 account identifier.") + } + self = account + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(absoluteString) + } +} diff --git a/Tests/WalletConnectTests/AccountTests.swift b/Tests/WalletConnectTests/AccountTests.swift index 7181899ad..528a0e71d 100644 --- a/Tests/WalletConnectTests/AccountTests.swift +++ b/Tests/WalletConnectTests/AccountTests.swift @@ -5,23 +5,30 @@ final class AccountTests: XCTestCase { func testInit() { // Valid accounts - XCTAssertNotNil(Account(string: "std:0:0")) - XCTAssertNotNil(Account(string: "chainstd:8c3444cf8970a9e41a706fab93e7a6c4:6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7")) + XCTAssertNotNil(Account("std:0:0")) + XCTAssertNotNil(Account("chainstd:8c3444cf8970a9e41a706fab93e7a6c4:6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7")) // Invalid accounts - XCTAssertNil(Account(string: "std:0:$")) - XCTAssertNil(Account(string: "std:$:0")) - XCTAssertNil(Account(string: "st:0:0")) + XCTAssertNil(Account("std:0:$")) + XCTAssertNil(Account("std:$:0")) + XCTAssertNil(Account("st:0:0")) } func testBlockchainIdentifier() { - let account = Account(string: "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb") - XCTAssertEqual(account?.blockchainIdentifier, "eip155:1") + let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! + XCTAssertEqual(account.blockchainIdentifier, "eip155:1") } func testAbsoluteString() { let accountString = "eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb" - let account = Account(string: accountString) - XCTAssertEqual(account?.absoluteString, accountString) + let account = Account(accountString)! + XCTAssertEqual(account.absoluteString, accountString) + } + + func testCodable() throws { + let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! + let encoded = try JSONEncoder().encode(account) + let decoded = try JSONDecoder().decode(Account.self, from: encoded) + XCTAssertEqual(account, decoded) } } From 6c254a0990f13042efbb1d6d3b8cba23c0218ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Thu, 3 Feb 2022 23:02:21 -0300 Subject: [PATCH 05/16] Replaced update calls string params with account type --- Example/DApp/ClientDelegate.swift | 2 +- .../Responder/ResponderViewController.swift | 6 +++--- Sources/WalletConnect/Account.swift | 2 +- .../WalletConnect/WalletConnectClient.swift | 19 ++++++++++--------- .../WalletConnectClientDelegate.swift | 2 +- Tests/IntegrationTests/ClientDelegate.swift | 4 ++-- Tests/IntegrationTests/ClientTest.swift | 10 +++++----- 7 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Example/DApp/ClientDelegate.swift b/Example/DApp/ClientDelegate.swift index 2fecc1d15..f64f674dd 100644 --- a/Example/DApp/ClientDelegate.swift +++ b/Example/DApp/ClientDelegate.swift @@ -34,7 +34,7 @@ class ClientDelegate: WalletConnectClientDelegate { onSessionResponse?(sessionResponse) } - func didUpdate(sessionTopic: String, accounts: Set) { + func didUpdate(sessionTopic: String, accounts: Set) { } func didUpgrade(sessionTopic: String, permissions: Session.Permissions) { diff --git a/Example/ExampleApp/Responder/ResponderViewController.swift b/Example/ExampleApp/Responder/ResponderViewController.swift index c5e662ca6..7b8bf90c0 100644 --- a/Example/ExampleApp/Responder/ResponderViewController.swift +++ b/Example/ExampleApp/Responder/ResponderViewController.swift @@ -151,8 +151,8 @@ extension ResponderViewController: SessionViewControllerDelegate { print("[RESPONDER] Approving session...") let proposal = currentProposal! currentProposal = nil - let accounts = proposal.permissions.blockchains.map {$0+":\(account)"} - client.approve(proposal: proposal, accounts: Set(accounts)) + let accounts = Set(proposal.permissions.blockchains.compactMap { Account($0+":\(account)") }) + client.approve(proposal: proposal, accounts: accounts) } func didRejectSession() { @@ -197,7 +197,7 @@ extension ResponderViewController: WalletConnectClientDelegate { } - func didUpdate(sessionTopic: String, accounts: Set) { + func didUpdate(sessionTopic: String, accounts: Set) { } diff --git a/Sources/WalletConnect/Account.swift b/Sources/WalletConnect/Account.swift index c83865116..ff258dea5 100644 --- a/Sources/WalletConnect/Account.swift +++ b/Sources/WalletConnect/Account.swift @@ -10,7 +10,7 @@ [CAIP-2]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md [CAIP-10]:https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md */ -public struct Account: Equatable { +public struct Account: Equatable, Hashable { /// A blockchain namespace. Usually describes an ecosystem or standard. public var namespace: String diff --git a/Sources/WalletConnect/WalletConnectClient.swift b/Sources/WalletConnect/WalletConnectClient.swift index ddb239ff8..786c1e78c 100644 --- a/Sources/WalletConnect/WalletConnectClient.swift +++ b/Sources/WalletConnect/WalletConnectClient.swift @@ -140,8 +140,8 @@ public final class WalletConnectClient { /// - Parameters: /// - proposal: Session Proposal received from peer client in a WalletConnect delegate function: `didReceive(sessionProposal: Session.Proposal)` /// - accounts: A Set of accounts that the dapp will be allowed to request methods executions on. - public func approve(proposal: Session.Proposal, accounts: Set) { - sessionEngine.approve(proposal: proposal.proposal, accounts: accounts) + public func approve(proposal: Session.Proposal, accounts: Set) { + sessionEngine.approve(proposal: proposal.proposal, accounts: Set(accounts.map { $0.absoluteString })) } /// For the responder to reject a session proposal. @@ -156,12 +156,13 @@ public final class WalletConnectClient { /// - Parameters: /// - topic: Topic of the session that is intended to be updated. /// - accounts: Set of accounts that will be allowed to be used by the session after the update. - public func update(topic: String, accounts: Set) { - do { - try sessionEngine.update(topic: topic, accounts: accounts) - } catch { - print("Error on session update call: \(error)") - } + public func update(topic: String, accounts: Set) throws { + try sessionEngine.update(topic: topic, accounts: Set(accounts.map { $0.absoluteString })) +// do { +// try sessionEngine.update(topic: topic, accounts: accounts) +// } catch { +// print("Error on session update call: \(error)") +// } } /// For the responder to upgrade session permissions @@ -308,7 +309,7 @@ public final class WalletConnectClient { delegate?.didUpgrade(sessionTopic: topic, permissions: upgradedPermissions) } sessionEngine.onSessionUpdate = { [unowned self] topic, accounts in - delegate?.didUpdate(sessionTopic: topic, accounts: accounts) + delegate?.didUpdate(sessionTopic: topic, accounts: Set(accounts.compactMap { Account($0) })) } sessionEngine.onNotificationReceived = { [unowned self] topic, notification in delegate?.didReceive(notification: notification, sessionTopic: topic) diff --git a/Sources/WalletConnect/WalletConnectClientDelegate.swift b/Sources/WalletConnect/WalletConnectClientDelegate.swift index a9df37c0a..b235d0320 100644 --- a/Sources/WalletConnect/WalletConnectClientDelegate.swift +++ b/Sources/WalletConnect/WalletConnectClientDelegate.swift @@ -36,7 +36,7 @@ public protocol WalletConnectClientDelegate: AnyObject { /// Tells the delegate that extra accounts has been included in session sequence /// /// Function is executed on controller and non-controller client when both communicating peers have successfully included new accounts requested by the controller client. - func didUpdate(sessionTopic: String, accounts: Set) + func didUpdate(sessionTopic: String, accounts: Set) /// Tells the delegate that the client has settled a session. /// diff --git a/Tests/IntegrationTests/ClientDelegate.swift b/Tests/IntegrationTests/ClientDelegate.swift index f5eacaeb0..523beb6f5 100644 --- a/Tests/IntegrationTests/ClientDelegate.swift +++ b/Tests/IntegrationTests/ClientDelegate.swift @@ -12,7 +12,7 @@ class ClientDelegate: WalletConnectClientDelegate { var onSessionRejected: ((String, Reason)->())? var onSessionDelete: (()->())? var onSessionUpgrade: ((String, Session.Permissions)->())? - var onSessionUpdate: ((String, Set)->())? + var onSessionUpdate: ((String, Set)->())? var onNotificationReceived: ((Session.Notification, String)->())? var onPairingUpdate: ((String, AppMetadata)->())? @@ -42,7 +42,7 @@ class ClientDelegate: WalletConnectClientDelegate { func didUpgrade(sessionTopic: String, permissions: Session.Permissions) { onSessionUpgrade?(sessionTopic, permissions) } - func didUpdate(sessionTopic: String, accounts: Set) { + func didUpdate(sessionTopic: String, accounts: Set) { onSessionUpdate?(sessionTopic, accounts) } func didReceive(notification: Session.Notification, sessionTopic: String) { diff --git a/Tests/IntegrationTests/ClientTest.swift b/Tests/IntegrationTests/ClientTest.swift index ecf9a5a83..4d348b7b2 100644 --- a/Tests/IntegrationTests/ClientTest.swift +++ b/Tests/IntegrationTests/ClientTest.swift @@ -57,7 +57,7 @@ final class ClientTests: XCTestCase { func testNewSession() { let proposerSettlesSessionExpectation = expectation(description: "Proposer settles session") let responderSettlesSessionExpectation = expectation(description: "Responder settles session") - let account = "0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" + let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! let permissions = Session.Permissions.stub() let uri = try! proposer.client.connect(sessionPermissions: permissions)! @@ -245,7 +245,7 @@ final class ClientTests: XCTestCase { func testSuccessfulSessionUpgrade() { let proposerSessionUpgradeExpectation = expectation(description: "Proposer upgrades session on responder request") let responderSessionUpgradeExpectation = expectation(description: "Responder upgrades session on proposer response") - let account = "0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" + let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! let permissions = Session.Permissions.stub() let upgradePermissions = Session.Permissions(blockchains: ["eip155:42"], methods: ["eth_sendTransaction"]) let uri = try! proposer.client.connect(sessionPermissions: permissions)! @@ -272,8 +272,8 @@ final class ClientTests: XCTestCase { func testSuccessfulSessionUpdate() { let proposerSessionUpdateExpectation = expectation(description: "Proposer updates session on responder request") let responderSessionUpdateExpectation = expectation(description: "Responder updates session on proposer response") - let account = "eip155:42:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" - let updateAccounts: Set = ["eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb"] + let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! + let updateAccounts: Set = [Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdf")!] let permissions = Session.Permissions.stub() let uri = try! proposer.client.connect(sessionPermissions: permissions)! try! responder.client.pair(uri: uri) @@ -281,7 +281,7 @@ final class ClientTests: XCTestCase { self.responder.client.approve(proposal: proposal, accounts: [account]) } responder.onSessionSettled = { [unowned self] sessionSettled in - responder.client.update(topic: sessionSettled.topic, accounts: updateAccounts) + try? responder.client.update(topic: sessionSettled.topic, accounts: updateAccounts) } responder.onSessionUpdate = { _, accounts in XCTAssertEqual(accounts, updateAccounts) From 817830e8284b4166d545468b056c8a259648d789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 4 Feb 2022 17:23:00 -0300 Subject: [PATCH 06/16] Add error responses to pairing payload method --- .../WalletConnect/Engine/PairingEngine.swift | 33 +++++++++++-------- Sources/WalletConnect/Types/ReasonCode.swift | 8 +++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/Sources/WalletConnect/Engine/PairingEngine.swift b/Sources/WalletConnect/Engine/PairingEngine.swift index 29736ef5a..35f2a47ca 100644 --- a/Sources/WalletConnect/Engine/PairingEngine.swift +++ b/Sources/WalletConnect/Engine/PairingEngine.swift @@ -175,7 +175,7 @@ final class PairingEngine { case .pairingUpdate(let updateParams): handlePairingUpdate(params: updateParams, topic: topic, requestId: requestId) case .pairingPayload(let pairingPayload): - self.handlePairingPayload(pairingPayload, for: topic, requestId: requestId) + self.handlePairingPayload(subscriptionPayload, payloadParams: pairingPayload) case .pairingPing(_): self.handlePairingPing(topic: topic, requestId: requestId) default: @@ -214,24 +214,29 @@ final class PairingEngine { } } - private func handlePairingPayload(_ payload: PairingType.PayloadParams, for topic: String, requestId: Int64) { - logger.debug("Will handle pairing payload") - guard sequencesStore.hasSequence(forTopic: topic) else { - logger.error("Pairing for the topic: \(topic) does not exist") + private func handlePairingPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: PairingType.PayloadParams) { + guard sequencesStore.hasSequence(forTopic: payload.topic) else { + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: payload.topic)) return } - guard payload.request.method == PairingType.PayloadMethods.sessionPropose else { - logger.error("Forbidden WCPairingPayload method") + guard payloadParams.request.method == PairingType.PayloadMethods.sessionPropose else { + relayer.respondError(for: payload, reason: .unauthorizedRPCMethod(payloadParams.request.method.rawValue)) return } - let sessionProposal = payload.request.params - if let pairingAgreementSecret = try? crypto.getAgreementSecret(for: sessionProposal.signal.params.topic) { - try? crypto.setAgreementSecret(pairingAgreementSecret, topic: sessionProposal.topic) - } - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: topic, response: JsonRpcResult.response(response)) { [weak self] error in - self?.onSessionProposal?(sessionProposal) + let sessionProposal = payloadParams.request.params + do { + if let pairingAgreementSecret = try crypto.getAgreementSecret(for: sessionProposal.signal.params.topic) { + try crypto.setAgreementSecret(pairingAgreementSecret, topic: sessionProposal.topic) + } else { + relayer.respondError(for: payload, reason: .missingOrInvalid("agreement keys")) + return + } + } catch { + relayer.respondError(for: payload, reason: .missingOrInvalid("agreement keys")) + return } + relayer.respondSuccess(for: payload) + onSessionProposal?(sessionProposal) } private func handlePairingApprove(approveParams: PairingType.ApprovalParams, pendingPairingTopic: String, requestId: Int64) { diff --git a/Sources/WalletConnect/Types/ReasonCode.swift b/Sources/WalletConnect/Types/ReasonCode.swift index 187095d47..b8f5d0c9e 100644 --- a/Sources/WalletConnect/Types/ReasonCode.swift +++ b/Sources/WalletConnect/Types/ReasonCode.swift @@ -9,11 +9,13 @@ enum ReasonCode { case generic(message: String) // 1000 (Internal) + case missingOrInvalid(String) case invalidUpdateRequest(context: Context) case invalidUpgradeRequest(context: Context) case noContextWithTopic(context: Context, topic: String) // 3000 (Unauthorized) + case unauthorizedRPCMethod(String) case unauthorizedUpdateRequest(context: Context) case unauthorizedUpgradeRequest(context: Context) case unauthorizedMatchingController(isController: Bool) @@ -21,9 +23,11 @@ enum ReasonCode { var code: Int { switch self { case .generic: return 0 + case .missingOrInvalid: return 1000 case .invalidUpdateRequest: return 1003 case .invalidUpgradeRequest: return 1004 case .noContextWithTopic: return 1301 + case .unauthorizedRPCMethod: return 3001 case .unauthorizedUpdateRequest: return 3003 case .unauthorizedUpgradeRequest: return 3004 case .unauthorizedMatchingController: return 3005 @@ -34,12 +38,16 @@ enum ReasonCode { switch self { case .generic(let message): return message + case .missingOrInvalid(let name): + return "Missing or invalid \(name)" case .invalidUpdateRequest(let context): return "Invalid \(context) update request" case .invalidUpgradeRequest(let context): return "Invalid \(context) upgrade request" case .noContextWithTopic(let context, let topic): return "No matching \(context) with topic: \(topic)" + case .unauthorizedRPCMethod(let method): + return "Unauthorized JSON-RPC method requested: \(method)" case .unauthorizedUpdateRequest(let context): return "Unauthorized \(context) update request" case .unauthorizedUpgradeRequest(let context): From 136cb1cd9683654efdda823c8f749408c6a825a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 4 Feb 2022 17:46:01 -0300 Subject: [PATCH 07/16] Changed pairing wc methods to respond w/o completion --- .../WalletConnect/Engine/PairingEngine.swift | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/Sources/WalletConnect/Engine/PairingEngine.swift b/Sources/WalletConnect/Engine/PairingEngine.swift index 35f2a47ca..94a096155 100644 --- a/Sources/WalletConnect/Engine/PairingEngine.swift +++ b/Sources/WalletConnect/Engine/PairingEngine.swift @@ -167,51 +167,41 @@ final class PairingEngine { private func setUpWCRequestHandling() { wcSubscriber.onReceivePayload = { [unowned self] subscriptionPayload in - let requestId = subscriptionPayload.wcRequest.id - let topic = subscriptionPayload.topic switch subscriptionPayload.wcRequest.params { case .pairingApprove(let approveParams): - handlePairingApprove(approveParams: approveParams, pendingPairingTopic: topic, requestId: requestId) + handlePairingApprove(payload: subscriptionPayload, approveParams: approveParams) case .pairingUpdate(let updateParams): - handlePairingUpdate(params: updateParams, topic: topic, requestId: requestId) + handlePairingUpdate(subscriptionPayload, updateParams: updateParams) case .pairingPayload(let pairingPayload): self.handlePairingPayload(subscriptionPayload, payloadParams: pairingPayload) case .pairingPing(_): - self.handlePairingPing(topic: topic, requestId: requestId) + self.handlePairingPing(subscriptionPayload) default: logger.warn("Warning: Pairing Engine - Unexpected method type: \(subscriptionPayload.wcRequest.method) received from subscriber") } } } - private func handlePairingUpdate(params: PairingType.UpdateParams,topic: String, requestId: Int64) { + private func handlePairingUpdate(_ payload: WCRequestSubscriptionPayload, updateParams: PairingType.UpdateParams) { + let topic = payload.topic guard var pairing = try? sequencesStore.getSequence(forTopic: topic) else { - logger.debug("Could not find pairing for topic \(topic)") + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: topic)) return } guard pairing.peerIsController else { - let error = WalletConnectError.unauthrorized(.unauthorizedUpdateRequest) - logger.error(error) - respond(error: error, requestId: requestId, topic: topic) + relayer.respondError(for: payload, reason: .unauthorizedUpdateRequest(context: .pairing)) return } - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: topic, response: JsonRpcResult.response(response)) { [unowned self] error in - if let error = error { - logger.error(error) - } else { - pairing.settled?.state = params.state - sequencesStore.setSequence(pairing) - onPairingUpdate?(topic, params.state.metadata) - } - } + + pairing.settled?.state = updateParams.state + sequencesStore.setSequence(pairing) + + relayer.respondSuccess(for: payload) + onPairingUpdate?(topic, updateParams.state.metadata) } - private func handlePairingPing(topic: String, requestId: Int64) { - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: topic, response: JsonRpcResult.response(response)) { error in - //todo - } + private func handlePairingPing(_ payload: WCRequestSubscriptionPayload) { + relayer.respondSuccess(for: payload) } private func handlePairingPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: PairingType.PayloadParams) { @@ -239,9 +229,10 @@ final class PairingEngine { onSessionProposal?(sessionProposal) } - private func handlePairingApprove(approveParams: PairingType.ApprovalParams, pendingPairingTopic: String, requestId: Int64) { - logger.debug("Responder Client approved pairing on topic: \(pendingPairingTopic)") + private func handlePairingApprove(payload: WCRequestSubscriptionPayload, approveParams: PairingType.ApprovalParams) { + let pendingPairingTopic = payload.topic guard let pairing = try? sequencesStore.getSequence(forTopic: pendingPairingTopic), let pendingPairing = pairing.pending else { + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: pendingPairingTopic)) return } @@ -263,14 +254,7 @@ final class PairingEngine { } sessionPermissions[pendingPairingTopic] = nil - // TODO: Move JSON-RPC responding to networking layer - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: proposal.topic, response: JsonRpcResult.response(response)) { [weak self] error in - if let error = error { - self?.logger.error("Could not respond with error: \(error)") - } - } - + relayer.respondSuccess(for: payload) onPairingApproved?(Pairing(topic: settledPairing.topic, peer: nil), permissions, settledPairing.relay) } From 0f15a0e417a9381be1675c76295ab32db930f157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 4 Feb 2022 18:11:09 -0300 Subject: [PATCH 08/16] Changed session wc method handling --- .../WalletConnect/Engine/SessionEngine.swift | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/Sources/WalletConnect/Engine/SessionEngine.swift b/Sources/WalletConnect/Engine/SessionEngine.swift index 529fc5810..84b77f7e7 100644 --- a/Sources/WalletConnect/Engine/SessionEngine.swift +++ b/Sources/WalletConnect/Engine/SessionEngine.swift @@ -259,51 +259,48 @@ final class SessionEngine { private func setUpWCRequestHandling() { wcSubscriber.onReceivePayload = { [unowned self] subscriptionPayload in - let requestId = subscriptionPayload.wcRequest.id - let topic = subscriptionPayload.topic switch subscriptionPayload.wcRequest.params { case .sessionApprove(let approveParams): - handleSessionApprove(approveParams, topic: topic, requestId: requestId) + handleSessionApprove(subscriptionPayload, approveParams: approveParams) case .sessionReject(let rejectParams): - handleSessionReject(rejectParams, topic: topic) + handleSessionReject(subscriptionPayload, rejectParams: rejectParams) case .sessionUpdate(let updateParams): handleSessionUpdate(payload: subscriptionPayload, updateParams: updateParams) case .sessionUpgrade(let upgradeParams): handleSessionUpgrade(payload: subscriptionPayload, upgradeParams: upgradeParams) case .sessionDelete(let deleteParams): - handleSessionDelete(deleteParams, topic: topic) + handleSessionDelete(subscriptionPayload, deleteParams: deleteParams) case .sessionPayload(let sessionPayloadParams): - handleSessionPayload(payloadParams: sessionPayloadParams, topic: topic, requestId: requestId) + handleSessionPayload(subscriptionPayload, payloadParams: sessionPayloadParams) case .sessionPing(_): - handleSessionPing(topic: topic, requestId: requestId) + handleSessionPing(subscriptionPayload) case .sessionNotification(let notificationParams): - handleSessionNotification(topic: topic, notificationParams: notificationParams, requestId: requestId) + handleSessionNotification(subscriptionPayload, notificationParams: notificationParams) default: logger.warn("Warning: Session Engine - Unexpected method type: \(subscriptionPayload.wcRequest.method) received from subscriber") } } } - private func handleSessionNotification(topic: String, notificationParams: SessionType.NotificationParams, requestId: Int64) { + // TODO: Add error responses + private func handleSessionNotification(_ payload: WCRequestSubscriptionPayload, notificationParams: SessionType.NotificationParams) { + let topic = payload.topic guard let session = sequencesStore.getSequence(forTopic: topic), session.isSettled else { + // respond error return } do { try validateNotification(session: session, params: notificationParams) - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: topic, response: JsonRpcResult.response(response)) { [unowned self] error in - if let error = error { - logger.error(error) - } else { - let notification = Session.Notification(type: notificationParams.type, data: notificationParams.data) - onNotificationReceived?(topic, notification) - } - } } catch let error as WalletConnectError { logger.error(error) - respond(error: error, requestId: requestId, topic: topic) - //on unauthorized notification received? - } catch {} + // respond error + return + } catch { + return + } + relayer.respondSuccess(for: payload) + let notification = Session.Notification(type: notificationParams.type, data: notificationParams.data) + onNotificationReceived?(topic, notification) } private func validateNotification(session: SessionSequence, params: SessionType.NotificationParams) throws { @@ -367,35 +364,37 @@ final class SessionEngine { onSessionUpgrade?(session.topic, newPermissions) } - private func handleSessionPing(topic: String, requestId: Int64) { - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: topic, response: .response(response)) { error in - //todo - } + private func handleSessionPing(_ payload: WCRequestSubscriptionPayload) { + relayer.respondSuccess(for: payload) } - private func handleSessionDelete(_ deleteParams: SessionType.DeleteParams, topic: String) { + private func handleSessionDelete(_ payload: WCRequestSubscriptionPayload, deleteParams: SessionType.DeleteParams) { + let topic = payload.topic guard sequencesStore.hasSequence(forTopic: topic) else { logger.debug("Could not find session for topic \(topic)") return } + relayer.respondSuccess(for: payload) sequencesStore.delete(topic: topic) wcSubscriber.removeSubscription(topic: topic) onSessionDelete?(topic, deleteParams.reason) } - private func handleSessionReject(_ rejectParams: SessionType.RejectParams, topic: String) { + private func handleSessionReject(_ payload: WCRequestSubscriptionPayload, rejectParams: SessionType.RejectParams) { + let topic = payload.topic guard sequencesStore.hasSequence(forTopic: topic) else { logger.debug("Could not find session for topic \(topic)") return } + relayer.respondSuccess(for: payload) sequencesStore.delete(topic: topic) wcSubscriber.removeSubscription(topic: topic) onSessionRejected?(topic, rejectParams.reason) } - private func handleSessionPayload(payloadParams: SessionType.PayloadParams, topic: String, requestId: Int64) { - let jsonRpcRequest = JSONRPCRequest(id: requestId, method: payloadParams.request.method, params: payloadParams.request.params) + private func handleSessionPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: SessionType.PayloadParams) { + let topic = payload.topic + let jsonRpcRequest = JSONRPCRequest(id: payload.wcRequest.id, method: payloadParams.request.method, params: payloadParams.request.params) let request = Request( id: jsonRpcRequest.id, topic: topic, @@ -407,7 +406,7 @@ final class SessionEngine { onSessionPayloadRequest?(request) } catch let error as WalletConnectError { logger.error(error) - respond(error: error, requestId: jsonRpcRequest.id, topic: topic) + relayer.respondError(for: payload, reason: .generic(message: "invalid request")) // TODO: Replace with accurate reason } catch {} } @@ -437,7 +436,8 @@ final class SessionEngine { } } - private func handleSessionApprove(_ approveParams: SessionType.ApproveParams, topic: String, requestId: Int64) { + private func handleSessionApprove(_ payload: WCRequestSubscriptionPayload, approveParams: SessionType.ApproveParams) { + let topic = payload.topic logger.debug("Responder Client approved session on topic: \(topic)") logger.debug("isController: \(isController)") guard !isController else { @@ -473,12 +473,7 @@ final class SessionEngine { blockchains: pendingSession.proposal.permissions.blockchain.chains, methods: pendingSession.proposal.permissions.jsonrpc.methods), accounts: settledSession.settled!.state.accounts) - let response = JSONRPCResponse(id: requestId, result: AnyCodable(true)) - relayer.respond(topic: topic, response: JsonRpcResult.response(response)) { [unowned self] error in - if let error = error { - logger.error(error) - } - } + relayer.respondSuccess(for: payload) onSessionApproved?(approvedSession) } From 47921dd6f43b132053308f30d1ab0199fe9a3d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 4 Feb 2022 18:12:32 -0300 Subject: [PATCH 09/16] Removed RPC error respond method from pairing engine --- Sources/WalletConnect/Engine/PairingEngine.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Sources/WalletConnect/Engine/PairingEngine.swift b/Sources/WalletConnect/Engine/PairingEngine.swift index 94a096155..f669df0c2 100644 --- a/Sources/WalletConnect/Engine/PairingEngine.swift +++ b/Sources/WalletConnect/Engine/PairingEngine.swift @@ -282,18 +282,6 @@ final class PairingEngine { } } - private func respond(error: WalletConnectError, requestId: Int64, topic: String) { - let jsonrpcError = JSONRPCErrorResponse.Error(code: error.code, message: error.description) - let response = JSONRPCErrorResponse(id: requestId, error: jsonrpcError) - relayer.respond(topic: topic, response: .error(response)) { [weak self] responseError in - if let responseError = responseError { - self?.logger.error("Could not respond with error: \(responseError)") - } else { - self?.logger.debug("successfully responded with error") - } - } - } - private func handleReponse(_ response: WCResponse) { switch response.requestParams { case .pairingApprove: From 5f887461d41a400a293b47b98e322dfb1f02b688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 4 Feb 2022 18:15:09 -0300 Subject: [PATCH 10/16] Removed RPC error respond method from session engine --- Sources/WalletConnect/Engine/SessionEngine.swift | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/Sources/WalletConnect/Engine/SessionEngine.swift b/Sources/WalletConnect/Engine/SessionEngine.swift index 84b77f7e7..fa39be082 100644 --- a/Sources/WalletConnect/Engine/SessionEngine.swift +++ b/Sources/WalletConnect/Engine/SessionEngine.swift @@ -409,18 +409,6 @@ final class SessionEngine { relayer.respondError(for: payload, reason: .generic(message: "invalid request")) // TODO: Replace with accurate reason } catch {} } - - private func respond(error: WalletConnectError, requestId: Int64, topic: String) { - let jsonrpcError = JSONRPCErrorResponse.Error(code: error.code, message: error.description) - let response = JSONRPCErrorResponse(id: requestId, error: jsonrpcError) - relayer.respond(topic: topic, response: JsonRpcResult.error(response)) { [weak self] responseError in - if let responseError = responseError { - self?.logger.error("Could not respond with error: \(responseError)") - } else { - self?.logger.debug("successfully responded with error") - } - } - } private func validatePayload(_ sessionRequest: Request) throws { guard let session = sequencesStore.getSequence(forTopic: sessionRequest.topic) else { From 6adcc53490531442cf7aded5c81f9b96e1aa7d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Fri, 4 Feb 2022 20:07:35 -0300 Subject: [PATCH 11/16] Renamed engine WC rpc methods --- .../WalletConnect/Engine/PairingEngine.swift | 72 ++++---- .../WalletConnect/Engine/SessionEngine.swift | 170 +++++++++--------- 2 files changed, 121 insertions(+), 121 deletions(-) diff --git a/Sources/WalletConnect/Engine/PairingEngine.swift b/Sources/WalletConnect/Engine/PairingEngine.swift index f669df0c2..27d5cc353 100644 --- a/Sources/WalletConnect/Engine/PairingEngine.swift +++ b/Sources/WalletConnect/Engine/PairingEngine.swift @@ -169,20 +169,49 @@ final class PairingEngine { wcSubscriber.onReceivePayload = { [unowned self] subscriptionPayload in switch subscriptionPayload.wcRequest.params { case .pairingApprove(let approveParams): - handlePairingApprove(payload: subscriptionPayload, approveParams: approveParams) + wcPairingApprove(subscriptionPayload, approveParams: approveParams) case .pairingUpdate(let updateParams): - handlePairingUpdate(subscriptionPayload, updateParams: updateParams) + wcPairingUpdate(subscriptionPayload, updateParams: updateParams) case .pairingPayload(let pairingPayload): - self.handlePairingPayload(subscriptionPayload, payloadParams: pairingPayload) + wcPairingPayload(subscriptionPayload, payloadParams: pairingPayload) case .pairingPing(_): - self.handlePairingPing(subscriptionPayload) + wcPairingPing(subscriptionPayload) default: logger.warn("Warning: Pairing Engine - Unexpected method type: \(subscriptionPayload.wcRequest.method) received from subscriber") } } } - private func handlePairingUpdate(_ payload: WCRequestSubscriptionPayload, updateParams: PairingType.UpdateParams) { + private func wcPairingApprove(_ payload: WCRequestSubscriptionPayload, approveParams: PairingType.ApprovalParams) { + let pendingPairingTopic = payload.topic + guard let pairing = try? sequencesStore.getSequence(forTopic: pendingPairingTopic), let pendingPairing = pairing.pending else { + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: pendingPairingTopic)) + return + } + + let agreementKeys = try! crypto.performKeyAgreement(selfPublicKey: try! pairing.getPublicKey(), peerPublicKey: approveParams.responder.publicKey) + + let settledTopic = agreementKeys.sharedSecret.sha256().toHexString() + try? crypto.setAgreementSecret(agreementKeys, topic: settledTopic) + let proposal = pendingPairing.proposal + let settledPairing = PairingSequence.buildAcknowledged(approval: approveParams, proposal: proposal, agreementKeys: agreementKeys) + + sequencesStore.setSequence(settledPairing) + sequencesStore.delete(topic: pendingPairingTopic) + wcSubscriber.setSubscription(topic: settledTopic) + wcSubscriber.removeSubscription(topic: proposal.topic) + + guard let permissions = sessionPermissions[pendingPairingTopic] else { + logger.debug("Cound not find permissions for pending topic: \(pendingPairingTopic)") + return + } + sessionPermissions[pendingPairingTopic] = nil + + relayer.respondSuccess(for: payload) + onPairingApproved?(Pairing(topic: settledPairing.topic, peer: nil), permissions, settledPairing.relay) + } + + private func wcPairingUpdate(_ payload: WCRequestSubscriptionPayload, updateParams: PairingType.UpdateParams) { let topic = payload.topic guard var pairing = try? sequencesStore.getSequence(forTopic: topic) else { relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: topic)) @@ -200,11 +229,7 @@ final class PairingEngine { onPairingUpdate?(topic, updateParams.state.metadata) } - private func handlePairingPing(_ payload: WCRequestSubscriptionPayload) { - relayer.respondSuccess(for: payload) - } - - private func handlePairingPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: PairingType.PayloadParams) { + private func wcPairingPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: PairingType.PayloadParams) { guard sequencesStore.hasSequence(forTopic: payload.topic) else { relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: payload.topic)) return @@ -229,33 +254,8 @@ final class PairingEngine { onSessionProposal?(sessionProposal) } - private func handlePairingApprove(payload: WCRequestSubscriptionPayload, approveParams: PairingType.ApprovalParams) { - let pendingPairingTopic = payload.topic - guard let pairing = try? sequencesStore.getSequence(forTopic: pendingPairingTopic), let pendingPairing = pairing.pending else { - relayer.respondError(for: payload, reason: .noContextWithTopic(context: .pairing, topic: pendingPairingTopic)) - return - } - - let agreementKeys = try! crypto.performKeyAgreement(selfPublicKey: try! pairing.getPublicKey(), peerPublicKey: approveParams.responder.publicKey) - - let settledTopic = agreementKeys.sharedSecret.sha256().toHexString() - try? crypto.setAgreementSecret(agreementKeys, topic: settledTopic) - let proposal = pendingPairing.proposal - let settledPairing = PairingSequence.buildAcknowledged(approval: approveParams, proposal: proposal, agreementKeys: agreementKeys) - - sequencesStore.setSequence(settledPairing) - sequencesStore.delete(topic: pendingPairingTopic) - wcSubscriber.setSubscription(topic: settledTopic) - wcSubscriber.removeSubscription(topic: proposal.topic) - - guard let permissions = sessionPermissions[pendingPairingTopic] else { - logger.debug("Cound not find permissions for pending topic: \(pendingPairingTopic)") - return - } - sessionPermissions[pendingPairingTopic] = nil - + private func wcPairingPing(_ payload: WCRequestSubscriptionPayload) { relayer.respondSuccess(for: payload) - onPairingApproved?(Pairing(topic: settledPairing.topic, peer: nil), permissions, settledPairing.relay) } private func removeRespondedPendingPairings() { diff --git a/Sources/WalletConnect/Engine/SessionEngine.swift b/Sources/WalletConnect/Engine/SessionEngine.swift index fa39be082..5b0137dfc 100644 --- a/Sources/WalletConnect/Engine/SessionEngine.swift +++ b/Sources/WalletConnect/Engine/SessionEngine.swift @@ -261,60 +261,81 @@ final class SessionEngine { wcSubscriber.onReceivePayload = { [unowned self] subscriptionPayload in switch subscriptionPayload.wcRequest.params { case .sessionApprove(let approveParams): - handleSessionApprove(subscriptionPayload, approveParams: approveParams) + wcSessionApprove(subscriptionPayload, approveParams: approveParams) case .sessionReject(let rejectParams): - handleSessionReject(subscriptionPayload, rejectParams: rejectParams) + wcSessionReject(subscriptionPayload, rejectParams: rejectParams) case .sessionUpdate(let updateParams): - handleSessionUpdate(payload: subscriptionPayload, updateParams: updateParams) + wcSessionUpdate(payload: subscriptionPayload, updateParams: updateParams) case .sessionUpgrade(let upgradeParams): - handleSessionUpgrade(payload: subscriptionPayload, upgradeParams: upgradeParams) + wcSessionUpgrade(payload: subscriptionPayload, upgradeParams: upgradeParams) case .sessionDelete(let deleteParams): - handleSessionDelete(subscriptionPayload, deleteParams: deleteParams) + wcSessionDelete(subscriptionPayload, deleteParams: deleteParams) case .sessionPayload(let sessionPayloadParams): - handleSessionPayload(subscriptionPayload, payloadParams: sessionPayloadParams) + wcSessionPayload(subscriptionPayload, payloadParams: sessionPayloadParams) case .sessionPing(_): - handleSessionPing(subscriptionPayload) + wcSessionPing(subscriptionPayload) case .sessionNotification(let notificationParams): - handleSessionNotification(subscriptionPayload, notificationParams: notificationParams) + wcSessionNotification(subscriptionPayload, notificationParams: notificationParams) default: logger.warn("Warning: Session Engine - Unexpected method type: \(subscriptionPayload.wcRequest.method) received from subscriber") } } } - // TODO: Add error responses - private func handleSessionNotification(_ payload: WCRequestSubscriptionPayload, notificationParams: SessionType.NotificationParams) { + private func wcSessionApprove(_ payload: WCRequestSubscriptionPayload, approveParams: SessionType.ApproveParams) { let topic = payload.topic - guard let session = sequencesStore.getSequence(forTopic: topic), session.isSettled else { - // respond error - return - } - do { - try validateNotification(session: session, params: notificationParams) - } catch let error as WalletConnectError { - logger.error(error) - // respond error - return - } catch { + logger.debug("Responder Client approved session on topic: \(topic)") + logger.debug("isController: \(isController)") + guard !isController else { + logger.warn("Warning: Session Engine - Unexpected handleSessionApprove method call by non Controller client") return } + guard let session = sequencesStore.getSequence(forTopic: topic), + let pendingSession = session.pending else { + logger.error("Could not find pending session for topic: \(topic)") + return + } + logger.debug("handleSessionApprove") + + let agreementKeys = try! crypto.performKeyAgreement(selfPublicKey: try! session.getPublicKey(), peerPublicKey: approveParams.responder.publicKey) + + let settledTopic = agreementKeys.derivedTopic() + + try! crypto.setAgreementSecret(agreementKeys, topic: settledTopic) + + let proposal = pendingSession.proposal + let settledSession = SessionSequence.buildAcknowledged(approval: approveParams, proposal: proposal, agreementKeys: agreementKeys, metadata: metadata) + + sequencesStore.delete(topic: proposal.topic) + sequencesStore.setSequence(settledSession) + + wcSubscriber.setSubscription(topic: settledTopic) + wcSubscriber.removeSubscription(topic: proposal.topic) + + let approvedSession = Session( + topic: settledTopic, + peer: approveParams.responder.metadata, + permissions: Session.Permissions( + blockchains: pendingSession.proposal.permissions.blockchain.chains, + methods: pendingSession.proposal.permissions.jsonrpc.methods), accounts: settledSession.settled!.state.accounts) + relayer.respondSuccess(for: payload) - let notification = Session.Notification(type: notificationParams.type, data: notificationParams.data) - onNotificationReceived?(topic, notification) + onSessionApproved?(approvedSession) } - private func validateNotification(session: SessionSequence, params: SessionType.NotificationParams) throws { - if session.isController { + private func wcSessionReject(_ payload: WCRequestSubscriptionPayload, rejectParams: SessionType.RejectParams) { + let topic = payload.topic + guard sequencesStore.hasSequence(forTopic: topic) else { + logger.debug("Could not find session for topic \(topic)") return - } else { - guard let notifications = session.settled?.permissions.notifications, - notifications.types.contains(params.type) else { - throw WalletConnectError.unauthrorized(.unauthorizedNotificationType) - } } + relayer.respondSuccess(for: payload) + sequencesStore.delete(topic: topic) + wcSubscriber.removeSubscription(topic: topic) + onSessionRejected?(topic, rejectParams.reason) } - private func handleSessionUpdate(payload: WCRequestSubscriptionPayload, updateParams: SessionType.UpdateParams) { + private func wcSessionUpdate(payload: WCRequestSubscriptionPayload, updateParams: SessionType.UpdateParams) { for account in updateParams.state.accounts { if !String.conformsToCAIP10(account) { relayer.respondError(for: payload, reason: .invalidUpdateRequest(context: .session)) @@ -340,7 +361,7 @@ final class SessionEngine { onSessionUpdate?(topic, updateParams.state.accounts) } - private func handleSessionUpgrade(payload: WCRequestSubscriptionPayload, upgradeParams: SessionType.UpgradeParams) { + private func wcSessionUpgrade(payload: WCRequestSubscriptionPayload, upgradeParams: SessionType.UpgradeParams) { guard validatePermissions(upgradeParams.permissions) else { relayer.respondError(for: payload, reason: .invalidUpgradeRequest(context: .session)) return @@ -364,11 +385,7 @@ final class SessionEngine { onSessionUpgrade?(session.topic, newPermissions) } - private func handleSessionPing(_ payload: WCRequestSubscriptionPayload) { - relayer.respondSuccess(for: payload) - } - - private func handleSessionDelete(_ payload: WCRequestSubscriptionPayload, deleteParams: SessionType.DeleteParams) { + private func wcSessionDelete(_ payload: WCRequestSubscriptionPayload, deleteParams: SessionType.DeleteParams) { let topic = payload.topic guard sequencesStore.hasSequence(forTopic: topic) else { logger.debug("Could not find session for topic \(topic)") @@ -380,19 +397,7 @@ final class SessionEngine { onSessionDelete?(topic, deleteParams.reason) } - private func handleSessionReject(_ payload: WCRequestSubscriptionPayload, rejectParams: SessionType.RejectParams) { - let topic = payload.topic - guard sequencesStore.hasSequence(forTopic: topic) else { - logger.debug("Could not find session for topic \(topic)") - return - } - relayer.respondSuccess(for: payload) - sequencesStore.delete(topic: topic) - wcSubscriber.removeSubscription(topic: topic) - onSessionRejected?(topic, rejectParams.reason) - } - - private func handleSessionPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: SessionType.PayloadParams) { + private func wcSessionPayload(_ payload: WCRequestSubscriptionPayload, payloadParams: SessionType.PayloadParams) { let topic = payload.topic let jsonRpcRequest = JSONRPCRequest(id: payload.wcRequest.id, method: payloadParams.request.method, params: payloadParams.request.params) let request = Request( @@ -424,45 +429,40 @@ final class SessionEngine { } } - private func handleSessionApprove(_ payload: WCRequestSubscriptionPayload, approveParams: SessionType.ApproveParams) { + private func wcSessionPing(_ payload: WCRequestSubscriptionPayload) { + relayer.respondSuccess(for: payload) + } + + // TODO: Add error responses + private func wcSessionNotification(_ payload: WCRequestSubscriptionPayload, notificationParams: SessionType.NotificationParams) { let topic = payload.topic - logger.debug("Responder Client approved session on topic: \(topic)") - logger.debug("isController: \(isController)") - guard !isController else { - logger.warn("Warning: Session Engine - Unexpected handleSessionApprove method call by non Controller client") + guard let session = sequencesStore.getSequence(forTopic: topic), session.isSettled else { + // respond error + return + } + do { + try validateNotification(session: session, params: notificationParams) + } catch let error as WalletConnectError { + logger.error(error) + // respond error + return + } catch { return } - guard let session = sequencesStore.getSequence(forTopic: topic), - let pendingSession = session.pending else { - logger.error("Could not find pending session for topic: \(topic)") - return - } - logger.debug("handleSessionApprove") - - let agreementKeys = try! crypto.performKeyAgreement(selfPublicKey: try! session.getPublicKey(), peerPublicKey: approveParams.responder.publicKey) - - let settledTopic = agreementKeys.derivedTopic() - - try! crypto.setAgreementSecret(agreementKeys, topic: settledTopic) - - let proposal = pendingSession.proposal - let settledSession = SessionSequence.buildAcknowledged(approval: approveParams, proposal: proposal, agreementKeys: agreementKeys, metadata: metadata) - - sequencesStore.delete(topic: proposal.topic) - sequencesStore.setSequence(settledSession) - - wcSubscriber.setSubscription(topic: settledTopic) - wcSubscriber.removeSubscription(topic: proposal.topic) - - let approvedSession = Session( - topic: settledTopic, - peer: approveParams.responder.metadata, - permissions: Session.Permissions( - blockchains: pendingSession.proposal.permissions.blockchain.chains, - methods: pendingSession.proposal.permissions.jsonrpc.methods), accounts: settledSession.settled!.state.accounts) - relayer.respondSuccess(for: payload) - onSessionApproved?(approvedSession) + let notification = Session.Notification(type: notificationParams.type, data: notificationParams.data) + onNotificationReceived?(topic, notification) + } + + private func validateNotification(session: SessionSequence, params: SessionType.NotificationParams) throws { + if session.isController { + return + } else { + guard let notifications = session.settled?.permissions.notifications, + notifications.types.contains(params.type) else { + throw WalletConnectError.unauthrorized(.unauthorizedNotificationType) + } + } } private func setupExpirationHandling() { From dc11c0250b782c8f4ee23274256b353757f76d8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 9 Feb 2022 17:46:09 -0300 Subject: [PATCH 12/16] Add error responses for wcSessionApprove --- .../WalletConnect/Engine/SessionEngine.swift | 44 +++++++++---------- .../Types/Session/SessionSequence.swift | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Sources/WalletConnect/Engine/SessionEngine.swift b/Sources/WalletConnect/Engine/SessionEngine.swift index 19cb75d36..faca57d3f 100644 --- a/Sources/WalletConnect/Engine/SessionEngine.swift +++ b/Sources/WalletConnect/Engine/SessionEngine.swift @@ -202,7 +202,7 @@ final class SessionEngine { throw WalletConnectError.internal(.notApproved) // TODO: Use a suitable error cases } } - if !session.isController || session.settled?.status != .acknowledged { + if !session.selfIsController || session.settled?.status != .acknowledged { throw WalletConnectError.unauthrorized(.unauthorizedUpdateRequest) } session.update(accounts) @@ -218,7 +218,7 @@ final class SessionEngine { guard session.isSettled else { throw WalletConnectError.sessionNotSettled(topic) } - guard session.isController else { + guard session.selfIsController else { throw WalletConnectError.unauthorizedNonControllerCall } guard validatePermissions(permissions) else { @@ -281,32 +281,31 @@ final class SessionEngine { private func wcSessionApprove(_ payload: WCRequestSubscriptionPayload, approveParams: SessionType.ApproveParams) { let topic = payload.topic - logger.debug("Responder Client approved session on topic: \(topic)") - guard let session = sequencesStore.getSequence(forTopic: topic), - let pendingSession = session.pending else { - logger.error("Could not find pending session for topic: \(topic)") - return - } - logger.debug("isController: \(session.isController)") - - guard !session.isController else { - logger.warn("Warning: Session Engine - Unexpected handleSessionApprove method call by non Controller client") + guard let session = sequencesStore.getSequence(forTopic: topic), let pendingSession = session.pending else { + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .session, topic: topic)) + return + } + guard !session.selfIsController else { + // TODO: Replace generic reason with a valid code. + relayer.respondError(for: payload, reason: .generic(message: "wcSessionApproval received by a controller")) return } - logger.debug("handleSessionApprove") - - let agreementKeys = try! crypto.performKeyAgreement(selfPublicKey: try! session.getPublicKey(), peerPublicKey: approveParams.responder.publicKey) - - let settledTopic = agreementKeys.derivedTopic() - - try! crypto.setAgreementSecret(agreementKeys, topic: settledTopic) + let settledTopic: String + do { + let publicKey = try session.getPublicKey() + let agreementKeys = try crypto.performKeyAgreement(selfPublicKey: publicKey, peerPublicKey: approveParams.responder.publicKey) + settledTopic = agreementKeys.derivedTopic() + try crypto.setAgreementSecret(agreementKeys, topic: settledTopic) + } catch { + relayer.respondError(for: payload, reason: .missingOrInvalid("agreement keys")) + return + } + let proposal = pendingSession.proposal let settledSession = SessionSequence.buildAcknowledged(approval: approveParams, proposal: proposal, agreementKeys: agreementKeys, metadata: metadata) - sequencesStore.delete(topic: proposal.topic) sequencesStore.setSequence(settledSession) - wcSubscriber.setSubscription(topic: settledTopic) wcSubscriber.removeSubscription(topic: proposal.topic) @@ -317,6 +316,7 @@ final class SessionEngine { blockchains: pendingSession.proposal.permissions.blockchain.chains, methods: pendingSession.proposal.permissions.jsonrpc.methods), accounts: settledSession.settled!.state.accounts) + logger.debug("Responder Client approved session on topic: \(topic)") relayer.respondSuccess(for: payload) onSessionApproved?(approvedSession) } @@ -445,7 +445,7 @@ final class SessionEngine { } private func validateNotification(session: SessionSequence, params: SessionType.NotificationParams) throws { - if session.isController { + if session.selfIsController { return } else { guard let notifications = session.settled?.permissions.notifications, diff --git a/Sources/WalletConnect/Types/Session/SessionSequence.swift b/Sources/WalletConnect/Types/Session/SessionSequence.swift index b54cf5158..c50e80da5 100644 --- a/Sources/WalletConnect/Types/Session/SessionSequence.swift +++ b/Sources/WalletConnect/Types/Session/SessionSequence.swift @@ -42,7 +42,7 @@ struct SessionSequence: ExpirableSequence { settled?.status == .acknowledged } - var isController: Bool { + var selfIsController: Bool { guard let controller = settled?.permissions.controller else { return false } return selfParticipant.publicKey == controller.publicKey } From 514277c66558b2526c345ebf477c84ab7bc8a490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 9 Feb 2022 18:31:44 -0300 Subject: [PATCH 13/16] Add missing errors and permission checks --- .../WalletConnect/Engine/SessionEngine.swift | 58 ++++++++----------- Sources/WalletConnect/Types/ReasonCode.swift | 8 +++ .../Types/Session/SessionSequence.swift | 5 ++ 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/Sources/WalletConnect/Engine/SessionEngine.swift b/Sources/WalletConnect/Engine/SessionEngine.swift index faca57d3f..a6ac0be19 100644 --- a/Sources/WalletConnect/Engine/SessionEngine.swift +++ b/Sources/WalletConnect/Engine/SessionEngine.swift @@ -292,9 +292,10 @@ final class SessionEngine { } let settledTopic: String + let agreementKeys: AgreementSecret do { let publicKey = try session.getPublicKey() - let agreementKeys = try crypto.performKeyAgreement(selfPublicKey: publicKey, peerPublicKey: approveParams.responder.publicKey) + agreementKeys = try crypto.performKeyAgreement(selfPublicKey: publicKey, peerPublicKey: approveParams.responder.publicKey) settledTopic = agreementKeys.derivedTopic() try crypto.setAgreementSecret(agreementKeys, topic: settledTopic) } catch { @@ -324,12 +325,12 @@ final class SessionEngine { private func wcSessionReject(_ payload: WCRequestSubscriptionPayload, rejectParams: SessionType.RejectParams) { let topic = payload.topic guard sequencesStore.hasSequence(forTopic: topic) else { - logger.debug("Could not find session for topic \(topic)") + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .session, topic: topic)) return } - relayer.respondSuccess(for: payload) sequencesStore.delete(topic: topic) wcSubscriber.removeSubscription(topic: topic) + relayer.respondSuccess(for: payload) onSessionRejected?(topic, rejectParams.reason) } @@ -378,12 +379,12 @@ final class SessionEngine { private func wcSessionDelete(_ payload: WCRequestSubscriptionPayload, deleteParams: SessionType.DeleteParams) { let topic = payload.topic guard sequencesStore.hasSequence(forTopic: topic) else { - logger.debug("Could not find session for topic \(topic)") + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .session, topic: topic)) return } - relayer.respondSuccess(for: payload) sequencesStore.delete(topic: topic) wcSubscriber.removeSubscription(topic: topic) + relayer.respondSuccess(for: payload) onSessionDelete?(topic, deleteParams.reason) } @@ -396,51 +397,42 @@ final class SessionEngine { method: jsonRpcRequest.method, params: jsonRpcRequest.params, chainId: payloadParams.chainId) - do { - try validatePayload(request) - onSessionPayloadRequest?(request) - } catch let error as WalletConnectError { - logger.error(error) - relayer.respondError(for: payload, reason: .generic(message: "invalid request")) // TODO: Replace with accurate reason - } catch {} - } - - private func validatePayload(_ sessionRequest: Request) throws { - guard let session = sequencesStore.getSequence(forTopic: sessionRequest.topic) else { - throw WalletConnectError.internal(.noSequenceForTopic) + + guard let session = sequencesStore.getSequence(forTopic: topic) else { + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .session, topic: topic)) + return } - if let chainId = sessionRequest.chainId { + if let chainId = request.chainId { guard session.hasPermission(forChain: chainId) else { - throw WalletConnectError.unauthrorized(.unauthorizedJsonRpcMethod) + relayer.respondError(for: payload, reason: .unauthorizedTargetChain(chainId)) + return } } - guard session.hasPermission(forMethod: sessionRequest.method) else { - throw WalletConnectError.unauthrorized(.unauthorizedJsonRpcMethod) + guard session.hasPermission(forMethod: request.method) else { + relayer.respondError(for: payload, reason: .unauthorizedRPCMethod(request.method)) + return } + onSessionPayloadRequest?(request) } private func wcSessionPing(_ payload: WCRequestSubscriptionPayload) { relayer.respondSuccess(for: payload) } - // TODO: Add error responses private func wcSessionNotification(_ payload: WCRequestSubscriptionPayload, notificationParams: SessionType.NotificationParams) { let topic = payload.topic guard let session = sequencesStore.getSequence(forTopic: topic), session.isSettled else { - // respond error + relayer.respondError(for: payload, reason: .noContextWithTopic(context: .session, topic: payload.topic)) return } - do { - try validateNotification(session: session, params: notificationParams) - } catch let error as WalletConnectError { - logger.error(error) - // respond error - return - } catch { - return + if session.selfIsController { + guard session.hasPermission(forNotification: notificationParams.type) else { + relayer.respondError(for: payload, reason: .unauthorizedNotificationType(notificationParams.type)) + return + } } - relayer.respondSuccess(for: payload) let notification = Session.Notification(type: notificationParams.type, data: notificationParams.data) + relayer.respondSuccess(for: payload) onNotificationReceived?(topic, notification) } @@ -511,7 +503,7 @@ final class SessionEngine { } switch result { case .response: - guard let settledSession = try? sequencesStore.getSequence(forTopic: settledTopic) else {return} + guard let settledSession = sequencesStore.getSequence(forTopic: settledTopic) else {return} crypto.deleteAgreementSecret(for: topic) wcSubscriber.removeSubscription(topic: topic) sequencesStore.delete(topic: topic) diff --git a/Sources/WalletConnect/Types/ReasonCode.swift b/Sources/WalletConnect/Types/ReasonCode.swift index b8f5d0c9e..4dcb1aa09 100644 --- a/Sources/WalletConnect/Types/ReasonCode.swift +++ b/Sources/WalletConnect/Types/ReasonCode.swift @@ -15,7 +15,9 @@ enum ReasonCode { case noContextWithTopic(context: Context, topic: String) // 3000 (Unauthorized) + case unauthorizedTargetChain(String) case unauthorizedRPCMethod(String) + case unauthorizedNotificationType(String) case unauthorizedUpdateRequest(context: Context) case unauthorizedUpgradeRequest(context: Context) case unauthorizedMatchingController(isController: Bool) @@ -27,7 +29,9 @@ enum ReasonCode { case .invalidUpdateRequest: return 1003 case .invalidUpgradeRequest: return 1004 case .noContextWithTopic: return 1301 + case .unauthorizedTargetChain: return 3000 case .unauthorizedRPCMethod: return 3001 + case .unauthorizedNotificationType: return 3002 case .unauthorizedUpdateRequest: return 3003 case .unauthorizedUpgradeRequest: return 3004 case .unauthorizedMatchingController: return 3005 @@ -46,8 +50,12 @@ enum ReasonCode { return "Invalid \(context) upgrade request" case .noContextWithTopic(let context, let topic): return "No matching \(context) with topic: \(topic)" + case .unauthorizedTargetChain(let chainId): + return "Unauthorized target chain id requested: \(chainId)" case .unauthorizedRPCMethod(let method): return "Unauthorized JSON-RPC method requested: \(method)" + case .unauthorizedNotificationType(let type): + return "Unauthorized notification type requested: \(type)" case .unauthorizedUpdateRequest(let context): return "Unauthorized \(context) update request" case .unauthorizedUpgradeRequest(let context): diff --git a/Sources/WalletConnect/Types/Session/SessionSequence.swift b/Sources/WalletConnect/Types/Session/SessionSequence.swift index c50e80da5..0ae11da31 100644 --- a/Sources/WalletConnect/Types/Session/SessionSequence.swift +++ b/Sources/WalletConnect/Types/Session/SessionSequence.swift @@ -73,6 +73,11 @@ struct SessionSequence: ExpirableSequence { return settled.permissions.jsonrpc.methods.contains(method) } + func hasPermission(forNotification type: String) -> Bool { + guard let notificationPermissions = settled?.permissions.notifications else { return false } + return notificationPermissions.types.contains(type) + } + mutating func upgrade(_ permissions: SessionPermissions) { settled?.permissions.upgrade(with: permissions) } From 36621567f0af942d9331cf918ea785c23e83cfb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Wed, 9 Feb 2022 18:34:47 -0300 Subject: [PATCH 14/16] Removed unused commented code --- Sources/WalletConnect/WalletConnectClient.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/WalletConnect/WalletConnectClient.swift b/Sources/WalletConnect/WalletConnectClient.swift index 6b36b3121..be8d586ba 100644 --- a/Sources/WalletConnect/WalletConnectClient.swift +++ b/Sources/WalletConnect/WalletConnectClient.swift @@ -155,11 +155,6 @@ public final class WalletConnectClient { /// - accounts: Set of accounts that will be allowed to be used by the session after the update. public func update(topic: String, accounts: Set) throws { try sessionEngine.update(topic: topic, accounts: Set(accounts.map { $0.absoluteString })) -// do { -// try sessionEngine.update(topic: topic, accounts: accounts) -// } catch { -// print("Error on session update call: \(error)") -// } } /// For the responder to upgrade session permissions From dc241adbf0e7c645b00836de25c47f7fa180795f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Thu, 10 Feb 2022 15:00:19 -0300 Subject: [PATCH 15/16] New alternate initializers for Account type --- Sources/WalletConnect/Account.swift | 31 +++++++++++++++++---- Tests/WalletConnectTests/AccountTests.swift | 20 ++++++++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/Sources/WalletConnect/Account.swift b/Sources/WalletConnect/Account.swift index ff258dea5..60dfcd1aa 100644 --- a/Sources/WalletConnect/Account.swift +++ b/Sources/WalletConnect/Account.swift @@ -13,13 +13,13 @@ public struct Account: Equatable, Hashable { /// A blockchain namespace. Usually describes an ecosystem or standard. - public var namespace: String + public let namespace: String /// A reference string that identifies a blockchain within a given namespace. - public var reference: String + public let reference: String /// The account's address specific to the blockchain. - public var address: String + public let address: String /// The CAIP-2 blockchain identifier of the account. public var blockchainIdentifier: String { @@ -45,9 +45,28 @@ public struct Account: Equatable, Hashable { public init?(_ string: String) { guard String.conformsToCAIP10(string) else { return nil } let splits = string.split(separator: ":") - self.namespace = String(splits[0]) - self.reference = String(splits[1]) - self.address = String(splits[2]) + self.init(namespace: String(splits[0]), reference: String(splits[1]), address: String(splits[2])) + } + + /** + Creates an account instance from a chain ID and an address. + + This initializer returns nil if the `chainIdentifier` parameter doesn't represent a valid chain id in conformance with + [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md) or if the `address` format is invalid. + */ + public init?(chainIdentifier: String, address: String) { + self.init("\(chainIdentifier):\(address)") + } + + /** + Creates an account instance directly from the base components. + + This initializer bypass any checks to CAIP conformance, make sure to pass valid values as parameters. + */ + public init(namespace: String, reference: String, address: String) { + self.namespace = namespace + self.reference = reference + self.address = address } } diff --git a/Tests/WalletConnectTests/AccountTests.swift b/Tests/WalletConnectTests/AccountTests.swift index 528a0e71d..006cf9df4 100644 --- a/Tests/WalletConnectTests/AccountTests.swift +++ b/Tests/WalletConnectTests/AccountTests.swift @@ -3,7 +3,7 @@ import XCTest final class AccountTests: XCTestCase { - func testInit() { + func testInitFromString() { // Valid accounts XCTAssertNotNil(Account("std:0:0")) XCTAssertNotNil(Account("chainstd:8c3444cf8970a9e41a706fab93e7a6c4:6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7")) @@ -14,6 +14,24 @@ final class AccountTests: XCTestCase { XCTAssertNil(Account("st:0:0")) } + func testInitFromChainAndAddress() { + // Valid accounts + XCTAssertNotNil(Account(chainIdentifier: "std:0", address: "0")) + XCTAssertNotNil(Account(chainIdentifier: "chainstd:8c3444cf8970a9e41a706fab93e7a6c4", address: "6d9b0b4b9994e8a6afbd3dc3ed983cd51c755afb27cd1dc7825ef59c134a39f7")) + + // Invalid accounts + XCTAssertNil(Account(chainIdentifier: "std:0", address: "")) + XCTAssertNil(Account(chainIdentifier: "std", address: "0")) + } + + func testInitCAIP10Conformance() { + XCTAssertTrue(Account(namespace: "std", reference: "0", address: "0").isCAIP10Conformant) + + XCTAssertFalse(Account(namespace: "st", reference: "0", address: "0").isCAIP10Conformant) + XCTAssertFalse(Account(namespace: "std", reference: "", address: "0").isCAIP10Conformant) + XCTAssertFalse(Account(namespace: "std", reference: "0", address: "").isCAIP10Conformant) + } + func testBlockchainIdentifier() { let account = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdb")! XCTAssertEqual(account.blockchainIdentifier, "eip155:1") From 2e8ec8fd65812b06cca6d146c165a8f2488a5651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Vants?= Date: Thu, 10 Feb 2022 16:16:35 -0300 Subject: [PATCH 16/16] Removed unused secure storage class --- .../WalletConnect/Storage/SecureStorage.swift | 34 ----------- .../WalletConnect/WalletConnectClient.swift | 3 - .../SecureStorageTests.swift | 58 ------------------- 3 files changed, 95 deletions(-) delete mode 100644 Sources/WalletConnect/Storage/SecureStorage.swift delete mode 100644 Tests/WalletConnectTests/SecureStorageTests.swift diff --git a/Sources/WalletConnect/Storage/SecureStorage.swift b/Sources/WalletConnect/Storage/SecureStorage.swift deleted file mode 100644 index 76f8023ef..000000000 --- a/Sources/WalletConnect/Storage/SecureStorage.swift +++ /dev/null @@ -1,34 +0,0 @@ -import Foundation - -// TODO: Add methods for crypto key parameters -final class SecureStorage { - - private let keychain: KeychainStorageProtocol - private let queue: DispatchQueue - - private let api = "api-key" - - init(keychain: KeychainStorageProtocol, - dispatchQueue: DispatchQueue = DispatchQueue(label: "com.walletconnect.keychain.queue", qos: .default)) { - self.keychain = keychain - self.queue = dispatchQueue - } - - func set(_ value: T, forKey key: String) where T : GenericPasswordConvertible { - queue.async { [weak self] in - try? self?.keychain.add(value, forKey: key) - } - } - - func get(key: String) -> T? where T : GenericPasswordConvertible { - queue.sync { - try? keychain.read(key: key) - } - } - - func removeValue(forKey key: String) { - queue.async { [weak self] in - try? self?.keychain.delete(key: key) - } - } -} diff --git a/Sources/WalletConnect/WalletConnectClient.swift b/Sources/WalletConnect/WalletConnectClient.swift index be8d586ba..089e249c5 100644 --- a/Sources/WalletConnect/WalletConnectClient.swift +++ b/Sources/WalletConnect/WalletConnectClient.swift @@ -26,7 +26,6 @@ public final class WalletConnectClient { private let sessionEngine: SessionEngine private let relay: WalletConnectRelaying private let crypto: Crypto - private let secureStorage: SecureStorage private let pairingQueue = DispatchQueue(label: "com.walletconnect.sdk.client.pairing", qos: .userInitiated) private let history: JsonRpcHistory @@ -50,7 +49,6 @@ public final class WalletConnectClient { self.logger = logger // try? keychain.deleteAll() // Use for cleanup while lifecycles are not handled yet, but FIXME whenever self.crypto = Crypto(keychain: keychain) - self.secureStorage = SecureStorage(keychain: keychain) let relayer = Relayer(relayHost: relayHost, projectId: projectId, keyValueStorage: keyValueStorage, logger: logger) let serializer = JSONRPCSerializer(crypto: crypto) self.history = JsonRpcHistory(logger: logger, keyValueStore: KeyValueStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)) @@ -80,7 +78,6 @@ public final class WalletConnectClient { self.logger = logger // try? keychain.deleteAll() // Use for cleanup while lifecycles are not handled yet, but FIXME whenever self.crypto = Crypto(keychain: keychain) - self.secureStorage = SecureStorage(keychain: keychain) let serializer = JSONRPCSerializer(crypto: crypto) self.history = JsonRpcHistory(logger: logger, keyValueStore: KeyValueStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)) self.relay = WalletConnectRelay(networkRelayer: relayer, jsonRpcSerializer: serializer, logger: logger, jsonRpcHistory: history) diff --git a/Tests/WalletConnectTests/SecureStorageTests.swift b/Tests/WalletConnectTests/SecureStorageTests.swift deleted file mode 100644 index 61912c42e..000000000 --- a/Tests/WalletConnectTests/SecureStorageTests.swift +++ /dev/null @@ -1,58 +0,0 @@ -import XCTest -@testable import WalletConnect - -final class SecureStorageTests: XCTestCase { - - var sut: SecureStorage! - - var keychainMock: KeychainStorageMock! - - var testQueue: DispatchQueue! - - override func setUp() { - testQueue = DispatchQueue(label: "queue.test.storage") - keychainMock = KeychainStorageMock() - sut = SecureStorage(keychain: keychainMock, dispatchQueue: testQueue) - } - - override func tearDown() { - sut = nil - keychainMock = nil - testQueue = nil - } - - func testSet() { - sut.set("", forKey: "") - testQueue.sync { - XCTAssertTrue(keychainMock.didCallAdd) - } - } - - func testGet() { - let _: String? = sut.get(key: "") - testQueue.sync { - XCTAssertTrue(keychainMock.didCallRead) - } - } - - func testRemoveValue() { - sut.removeValue(forKey: "") - testQueue.sync { - XCTAssertTrue(keychainMock.didCallDelete) - } - } - - func testThreadSafeAccess() { - let count = 1000 - DispatchQueue.concurrentPerform(iterations: count) { i in - let key = "key\(i)" - let value = UUID().uuidString - sut.set(value, forKey: key) - let retrievedValue: String? = sut.get(key: key) - sut.removeValue(forKey: key) - let retrievedAfterDelete: String? = sut.get(key: key) - XCTAssertEqual(retrievedValue, value) - XCTAssertNil(retrievedAfterDelete) - } - } -}