Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:WalletConnect/WalletConnectSwift…
Browse files Browse the repository at this point in the history
…V2 into async-pair

# Conflicts:
#	Sources/WalletConnect/Engine/Common/SessionEngine.swift
#	Sources/WalletConnect/WalletConnectClient.swift
#	Tests/IntegrationTests/ClientTest.swift
#	Tests/WalletConnectTests/SessionEngineTests.swift
  • Loading branch information
llbartekll committed May 12, 2022
2 parents 1bf5645 + 3fd3b4b commit 2c84694
Show file tree
Hide file tree
Showing 14 changed files with 65 additions and 133 deletions.
2 changes: 1 addition & 1 deletion Example/ExampleApp/Wallet/WalletViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ extension WalletViewController: ProposalViewControllerDelegate {
let proposal = currentProposal!
currentProposal = nil
let accounts = Set(proposal.namespaces.first?.chains.compactMap { Account($0.absoluteString + ":\(account)") } ?? [])
client.approve(proposalId: proposal.id, accounts: accounts, namespaces: proposal.namespaces)
try! client.approve(proposalId: proposal.id, accounts: accounts, namespaces: proposal.namespaces)
}

func didRejectSession() {
Expand Down
1 change: 1 addition & 0 deletions Sources/WalletConnect/Engine/Common/PairingEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ final class PairingEngine {

func propose(pairingTopic: String, namespaces: Set<Namespace>, relay: RelayProtocolOptions) async throws {
logger.debug("Propose Session on topic: \(pairingTopic)")
try Namespace.validate(namespaces)
let publicKey = try! kms.createX25519KeyPair()
let proposer = Participant(
publicKey: publicKey.hexRepresentation,
Expand Down
27 changes: 11 additions & 16 deletions Sources/WalletConnect/Engine/Common/SessionEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ final class SessionEngine {
}
}

func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain?) async throws {
func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws {
guard let session = sessionStore.getSession(forTopic: topic), session.acknowledged else {
logger.debug("Could not find session for topic \(topic)")
return
Expand Down Expand Up @@ -140,7 +140,8 @@ final class SessionEngine {
}.store(in: &publishers)
}

func settle(topic: String, proposal: SessionProposal, accounts: Set<Account>, namespaces: Set<Namespace>) {
func settle(topic: String, proposal: SessionProposal, accounts: Set<Account>, namespaces: Set<Namespace>) throws {
try Namespace.validate(namespaces)
let agreementKeys = try! kms.getAgreementSecret(for: topic)!

let selfParticipant = Participant(publicKey: agreementKeys.publicKey.hexRepresentation, metadata: metadata)
Expand Down Expand Up @@ -212,20 +213,14 @@ final class SessionEngine {
networkingInteractor.respondError(for: payload, reason: .noContextWithTopic(context: .session, topic: topic))
return
}
if let chain = request.chainId {
guard session.hasNamespace(for: chain) else {
networkingInteractor.respondError(for: payload, reason: .unauthorizedTargetChain(chain.absoluteString))
return
}
guard session.hasNamespace(for: chain, method: request.method) else {
networkingInteractor.respondError(for: payload, reason: .unauthorizedMethod(request.method))
return
}
} else {
guard session.hasNamespace(for: nil, method: request.method) else {
networkingInteractor.respondError(for: payload, reason: .unauthorizedMethod(request.method))
return
}
let chain = request.chainId
guard session.hasNamespace(for: chain) else {
networkingInteractor.respondError(for: payload, reason: .unauthorizedTargetChain(chain.absoluteString))
return
}
guard session.hasNamespace(for: chain, method: request.method) else {
networkingInteractor.respondError(for: payload, reason: .unauthorizedMethod(request.method))
return
}
onSessionRequest?(request)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,6 @@ protocol SessionStateMachineValidating {

extension SessionStateMachineValidating {
func validateNamespaces(_ namespaces: Set<Namespace>) throws {
for namespace in namespaces {
for method in namespace.methods {
if method.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
throw WalletConnectError.invalidMethod
}
}
for event in namespace.events {
if event.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
throw WalletConnectError.invalidEvent
}
}
}
try Namespace.validate(namespaces)
}
}
21 changes: 21 additions & 0 deletions Sources/WalletConnect/Namespace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,24 @@ public struct Namespace: Codable, Equatable, Hashable {
self.events = events
}
}

internal extension Namespace {

static func validate(_ namespaces: Set<Namespace>) throws {
for namespace in namespaces {
guard !namespace.chains.isEmpty else {
throw WalletConnectError.namespaceHasEmptyChains
}
for method in namespace.methods {
if method.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
throw WalletConnectError.invalidMethod
}
}
for event in namespace.events {
if event.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
throw WalletConnectError.invalidEvent
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,6 @@ class NetworkInteractor: NetworkInteracting {

func getChainId(_ request: WCRequest) -> String? {
guard case let .sessionRequest(payload) = request.params else {return nil}
return payload.chainId?.absoluteString
return payload.chainId.absoluteString
}
}
6 changes: 3 additions & 3 deletions Sources/WalletConnect/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ public struct Request: Codable, Equatable {
public let topic: String
public let method: String
public let params: AnyCodable
public let chainId: Blockchain?
public let chainId: Blockchain

internal init(id: Int64, topic: String, method: String, params: AnyCodable, chainId: Blockchain?) {
internal init(id: Int64, topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
self.id = id
self.topic = topic
self.method = method
self.params = params
self.chainId = chainId
}

public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain?) {
public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
self.id = Self.generateId()
self.topic = topic
self.method = method
Expand Down
4 changes: 2 additions & 2 deletions Sources/WalletConnect/Types/Session/SessionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ internal enum SessionType {

struct RequestParams: Codable, Equatable {
let request: Request
let chainId: Blockchain?
let chainId: Blockchain

struct Request: Codable, Equatable {
let method: String
Expand All @@ -70,7 +70,7 @@ internal enum SessionType {

struct EventParams: Codable, Equatable {
let event: Event
let chainId: Blockchain?
let chainId: Blockchain

struct Event: Codable, Equatable {
let name: String
Expand Down
1 change: 1 addition & 0 deletions Sources/WalletConnect/Types/Session/WCSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ struct WCSession: ExpirableSequence {
namespaces.contains{$0.chains.contains(chain)}
}

// TODO: Remove optional for chain param, it's required now / protocol change
func hasNamespace(for chain: Blockchain?, method: String) -> Bool {
if let chain = chain {
let namespacesIncludingChain = namespaces.filter{$0.chains.contains(chain)}
Expand Down
7 changes: 4 additions & 3 deletions Sources/WalletConnect/WalletConnectClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,11 @@ public final class WalletConnectClient {
public func approve(
proposalId: String,
accounts: Set<Account>,
namespaces: Set<Namespace>) {
namespaces: Set<Namespace>
) throws {
//TODO - accounts should be validated for matching namespaces
guard let (sessionTopic, proposal) = pairingEngine.respondSessionPropose(proposerPubKey: proposalId) else {return}
sessionEngine.settle(topic: sessionTopic, proposal: proposal, accounts: accounts, namespaces: namespaces)
try sessionEngine.settle(topic: sessionTopic, proposal: proposal, accounts: accounts, namespaces: namespaces)
}

/// For the responder to reject a session proposal.
Expand Down Expand Up @@ -247,7 +248,7 @@ public final class WalletConnectClient {
/// - topic: Session topic
/// - params: Event Parameters
/// - completion: calls a handler upon completion
public func emit(topic: String, event: Session.Event, chainId: Blockchain?) async throws {
public func emit(topic: String, event: Session.Event, chainId: Blockchain) async throws {
try await sessionEngine.emit(topic: topic, event: event.internalRepresentation(), chainId: chainId)
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/WalletConnect/WalletConnectError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum WalletConnectError: Error {
case noSessionMatchingTopic(String)
case sessionNotAcknowledged(String)
case pairingNotSettled(String)
case namespaceHasEmptyChains
case invalidMethod
case invalidEvent
case invalidUpdateExpiryValue
Expand Down Expand Up @@ -39,6 +40,8 @@ extension WalletConnectError {
return "Pairing is not settled on topic \(topic)."
case .invalidUpdateExpiryValue:
return "Update expiry time is out of expected range"
case .namespaceHasEmptyChains:
return "Namespace has an empty list of chain IDs."
case .invalidMethod:
return "Methods set is invalid."
case .invalidEvent:
Expand Down
28 changes: 14 additions & 14 deletions Tests/IntegrationTests/ClientTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
try! await responder.client.pair(uri: uri)
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [account], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [account], namespaces: [])
}
responder.onSessionSettled = { sessionSettled in
// FIXME: Commented assertion
Expand All @@ -99,7 +99,7 @@ final class ClientTests: XCTestCase {
try! await responder.client.pair(uri: uri)

responder.onSessionProposal = { [unowned self] proposal in
responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
try? responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
}
responder.onSessionSettled = { sessionSettled in
responderSettlesSessionExpectation.fulfill()
Expand Down Expand Up @@ -139,7 +139,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
_ = try! await responder.client.pair(uri: uri)
responder.onSessionProposal = {[unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
}
proposer.onSessionSettled = {[unowned self] settledSession in
Task {
Expand All @@ -163,7 +163,7 @@ final class ClientTests: XCTestCase {

_ = try! await responder.client.pair(uri: uri)
responder.onSessionProposal = {[unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: proposal.namespaces)
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: proposal.namespaces)
}
proposer.onSessionSettled = {[unowned self] settledSession in
let requestParams = Request(id: 0, topic: settledSession.topic, method: method, params: AnyCodable(params), chainId: Blockchain("eip155:1")!)
Expand Down Expand Up @@ -202,7 +202,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub(methods: [method])])!
_ = try! await responder.client.pair(uri: uri)
responder.onSessionProposal = {[unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: proposal.namespaces)
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: proposal.namespaces)
}
proposer.onSessionSettled = {[unowned self] settledSession in
let requestParams = Request(id: 0, topic: settledSession.topic, method: method, params: AnyCodable(params), chainId: Blockchain("eip155:1")!)
Expand Down Expand Up @@ -234,7 +234,7 @@ final class ClientTests: XCTestCase {

try! await responder.client.pair(uri: uri)
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
}
proposer.onSessionSettled = { [unowned self] sessionSettled in
self.proposer.client.ping(topic: sessionSettled.topic) { response in
Expand All @@ -254,7 +254,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
try! await responder.client.pair(uri: uri)
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [account], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [account], namespaces: [])
}
responder.onSessionSettled = { [unowned self] sessionSettled in
try? responder.client.updateAccounts(topic: sessionSettled.topic, accounts: updateAccounts)
Expand All @@ -278,7 +278,7 @@ final class ClientTests: XCTestCase {
let namespacesToUpdateWith: Set<Namespace> = [Namespace(chains: [Blockchain("eip155:1")!, Blockchain("eip155:137")!], methods: ["xyz"], events: ["abc"])]
try! await responder.client.pair(uri: uri)
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
}
responder.onSessionSettled = { [unowned self] session in
try? responder.client.updateNamespaces(topic: session.topic, namespaces: namespacesToUpdateWith)
Expand All @@ -301,7 +301,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
try! await responder.client.pair(uri: uri)
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
}
responder.onSessionSettled = { [unowned self] session in
Thread.sleep(forTimeInterval: 1) //sleep because new expiry must be greater than current
Expand All @@ -319,16 +319,16 @@ final class ClientTests: XCTestCase {
func testSessionEventSucceeds() async {
await waitClientsConnected()
let proposerReceivesEventExpectation = expectation(description: "Proposer receives event")
let namespace = Namespace(chains: [], methods: [], events: ["type1"])
let namespace = Namespace(chains: [Blockchain("eip155:1")!], methods: [], events: ["type1"]) // TODO: Fix namespace with empty chain array / protocol change
let uri = try! await proposer.client.connect(namespaces: [namespace])!

try! await responder.client.pair(uri: uri)
let event = Session.Event(name: "type1", data: AnyCodable("event_data"))
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [namespace])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [namespace])
}
responder.onSessionSettled = { [unowned self] session in
Task{try? await responder.client.emit(topic: session.topic, event: event, chainId: nil)}
Task{try? await responder.client.emit(topic: session.topic, event: event, chainId: Blockchain("eip155:1")!)}
}
proposer.onEventReceived = { event, _ in
XCTAssertEqual(event, event)
Expand All @@ -346,10 +346,10 @@ final class ClientTests: XCTestCase {
try! await responder.client.pair(uri: uri)
let event = Session.Event(name: "type2", data: AnyCodable("event_data"))
responder.onSessionProposal = { [unowned self] proposal in
self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
try? self.responder.client.approve(proposalId: proposal.id, accounts: [], namespaces: [])
}
proposer.onSessionSettled = { [unowned self] session in
Task {await XCTAssertThrowsErrorAsync(try await proposer.client.emit(topic: session.topic, event: event, chainId: nil))}
Task {await XCTAssertThrowsErrorAsync(try await proposer.client.emit(topic: session.topic, event: event, chainId: Blockchain("eip155:1")!))}
}
responder.onEventReceived = { _, _ in
XCTFail()
Expand Down
Loading

0 comments on commit 2c84694

Please sign in to comment.