Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#201 required chainid #203

Merged
merged 5 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Example/ExampleApp/Wallet/WalletViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,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
6 changes: 6 additions & 0 deletions Sources/WalletConnect/Engine/PairingEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ final class PairingEngine {
}
func propose(pairingTopic: String, namespaces: Set<Namespace>, relay: RelayProtocolOptions, completion: @escaping ((Error?) -> ())) {
logger.debug("Propose Session on topic: \(pairingTopic)")
do {
try Namespace.validate(namespaces)
} catch {
completion(error)
return
}
let publicKey = try! kms.createX25519KeyPair()
let proposer = Participant(
publicKey: publicKey.hexRepresentation,
Expand Down
27 changes: 11 additions & 16 deletions Sources/WalletConnect/Engine/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?, completion: ((Error?)->())?) {
func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain, completion: ((Error?)->())?) {
guard let session = sessionStore.getSession(forTopic: topic), session.acknowledged else {
logger.debug("Could not find session for topic \(topic)")
return
Expand Down Expand Up @@ -145,7 +145,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 @@ -218,20 +219,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 @@ -170,10 +170,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 @@ -259,7 +260,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?, completion: ((Error?)->())?) {
public func emit(topic: String, event: Session.Event, chainId: Blockchain, completion: ((Error?)->())?) {
sessionEngine.emit(topic: topic, event: event.internalRepresentation(), chainId: chainId, completion: completion)
}

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personally I would prefer smth like: emptyChainsInNamespaceForbidden or invalidNamespaceStructure

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 @@ -55,7 +55,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
try! 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 @@ -81,7 +81,7 @@ final class ClientTests: XCTestCase {
try! 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 @@ -119,7 +119,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
_ = try! 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 @@ -142,7 +142,7 @@ final class ClientTests: XCTestCase {

_ = try! 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 @@ -180,7 +180,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub(methods: [method])])!
_ = try! 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 @@ -211,7 +211,7 @@ final class ClientTests: XCTestCase {

try! 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 @@ -230,7 +230,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
try! 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 @@ -253,7 +253,7 @@ final class ClientTests: XCTestCase {
let namespacesToUpdateWith: Set<Namespace> = [Namespace(chains: [Blockchain("eip155:1")!, Blockchain("eip155:137")!], methods: ["xyz"], events: ["abc"])]
try! 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 @@ -275,7 +275,7 @@ final class ClientTests: XCTestCase {
let uri = try! await proposer.client.connect(namespaces: [Namespace.stub()])!
try! 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 @@ -292,16 +292,16 @@ final class ClientTests: XCTestCase {

func testSessionEventSucceeds() async {
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! 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
responder.client.emit(topic: session.topic, event: event, chainId: nil, completion: nil)
responder.client.emit(topic: session.topic, event: event, chainId: Blockchain("eip155:1")!, completion: nil)
}
proposer.onEventReceived = { event, _ in
XCTAssertEqual(event, event)
Expand All @@ -318,10 +318,10 @@ final class ClientTests: XCTestCase {
try! 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
proposer.client.emit(topic: session.topic, event: event, chainId: nil) { error in
proposer.client.emit(topic: session.topic, event: event, chainId: Blockchain("eip155:1")!) { error in
XCTAssertNotNil(error)
}
}
Expand Down
Loading