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

[Bugfix] #340: Required namespaces check #342

Merged
merged 2 commits into from
Jul 15, 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: 2 additions & 0 deletions Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ final class ApproveEngine {
selfParticipant: selfParticipant,
peerParticipant: proposal.proposer,
settleParams: settleParams,
requiredNamespaces: proposal.requiredNamespaces,
acknowledged: false)

logger.debug("Sending session settle request")
Expand Down Expand Up @@ -310,6 +311,7 @@ private extension ApproveEngine {
selfParticipant: selfParticipant,
peerParticipant: settleParams.controller,
settleParams: settleParams,
requiredNamespaces: proposedNamespaces,
acknowledged: true
)
sessionStore.setSession(session)
Expand Down
22 changes: 18 additions & 4 deletions Sources/WalletConnectSign/Namespace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,15 @@ public struct SessionNamespace: Equatable, Codable {
self.events = events
}

func isSuperset(of other: Extension) -> Bool {
self.accounts.isSuperset(of: other.accounts) &&
self.methods.isSuperset(of: other.methods) &&
self.events.isSuperset(of: other.events)
func isCompliant(to required: ProposalNamespace.Extension) -> Bool {
guard
SessionNamespace.accountsAreCompliant(accounts, toChains: required.chains),
methods.isSuperset(of: required.methods),
events.isSuperset(of: required.events)
else {
return false
}
return true
}
}

Expand All @@ -58,6 +63,15 @@ public struct SessionNamespace: Equatable, Codable {
self.events = events
self.extensions = extensions
}

static func accountsAreCompliant(_ accounts: Set<Account>, toChains chains: Set<Blockchain>) -> Bool {
for chain in chains {
guard accounts.contains(where: { $0.blockchain == chain }) else {
return false
}
}
return true
}
}

enum Namespace {
Expand Down
28 changes: 21 additions & 7 deletions Sources/WalletConnectSign/Types/Session/WCSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct WCSession: SequenceObject, Equatable {
private(set) var expiryDate: Date
private(set) var timestamp: Date
private(set) var namespaces: [String: SessionNamespace]
private(set) var requiredNamespaces: [String: SessionNamespace]
private(set) var requiredNamespaces: [String: ProposalNamespace]

static var defaultTimeToLive: Int64 {
Int64(7*Time.day)
Expand All @@ -33,6 +33,7 @@ struct WCSession: SequenceObject, Equatable {
selfParticipant: Participant,
peerParticipant: Participant,
settleParams: SessionType.SettleParams,
requiredNamespaces: [String: ProposalNamespace],
acknowledged: Bool) {
self.topic = topic
self.timestamp = timestamp
Expand All @@ -41,21 +42,34 @@ struct WCSession: SequenceObject, Equatable {
self.selfParticipant = selfParticipant
self.peerParticipant = peerParticipant
self.namespaces = settleParams.namespaces
self.requiredNamespaces = settleParams.namespaces
self.requiredNamespaces = requiredNamespaces
self.acknowledged = acknowledged
self.expiryDate = Date(timeIntervalSince1970: TimeInterval(settleParams.expiry))
}

#if DEBUG
internal init(topic: String, timestamp: Date, relay: RelayProtocolOptions, controller: AgreementPeer, selfParticipant: Participant, peerParticipant: Participant, namespaces: [String: SessionNamespace], events: Set<String>, accounts: Set<Account>, acknowledged: Bool, expiry: Int64) {
internal init(
topic: String,
timestamp: Date,
relay: RelayProtocolOptions,
controller: AgreementPeer,
selfParticipant: Participant,
peerParticipant: Participant,
namespaces: [String: SessionNamespace],
requiredNamespaces: [String: ProposalNamespace],
events: Set<String>,
accounts: Set<Account>,
acknowledged: Bool,
expiry: Int64
) {
self.topic = topic
self.timestamp = timestamp
self.relay = relay
self.controller = controller
self.selfParticipant = selfParticipant
self.peerParticipant = peerParticipant
self.namespaces = namespaces
self.requiredNamespaces = namespaces
self.requiredNamespaces = requiredNamespaces
self.acknowledged = acknowledged
self.expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry))
}
Expand Down Expand Up @@ -121,7 +135,7 @@ struct WCSession: SequenceObject, Equatable {
for item in requiredNamespaces {
guard
let compliantNamespace = namespaces[item.key],
compliantNamespace.accounts.isSuperset(of: item.value.accounts),
SessionNamespace.accountsAreCompliant(compliantNamespace.accounts, toChains: item.value.chains),
compliantNamespace.methods.isSuperset(of: item.value.methods),
compliantNamespace.events.isSuperset(of: item.value.events)
else {
Expand All @@ -132,7 +146,7 @@ struct WCSession: SequenceObject, Equatable {
throw Error.unsatisfiedUpdateNamespaceRequirement
}
for existingExtension in extensions {
guard compliantExtensions.contains(where: { $0.isSuperset(of: existingExtension) }) else {
guard compliantExtensions.contains(where: { $0.isCompliant(to: existingExtension) }) else {
throw Error.unsatisfiedUpdateNamespaceRequirement
}
}
Expand Down Expand Up @@ -194,7 +208,7 @@ extension WCSession {

// Migration beta.102
self.timestamp = try container.decodeIfPresent(Date.self, forKey: .timestamp) ?? .distantPast
self.requiredNamespaces = try container.decodeIfPresent([String: SessionNamespace].self, forKey: .requiredNamespaces) ?? [:]
self.requiredNamespaces = try container.decodeIfPresent([String: ProposalNamespace].self, forKey: .requiredNamespaces) ?? [:]
}

func encode(to encoder: Encoder) throws {
Expand Down
2 changes: 2 additions & 0 deletions Tests/WalletConnectSignTests/Stub/Session+Stub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extension WCSession {
expiryDate: Date = Date.distantFuture,
selfPrivateKey: AgreementPrivateKey = AgreementPrivateKey(),
namespaces: [String: SessionNamespace] = [:],
requiredNamespaces: [String: ProposalNamespace] = [:],
acknowledged: Bool = true,
timestamp: Date = Date()
) -> WCSession {
Expand All @@ -23,6 +24,7 @@ extension WCSession {
selfParticipant: Participant.stub(publicKey: selfKey),
peerParticipant: Participant.stub(publicKey: peerKey),
namespaces: namespaces,
requiredNamespaces: requiredNamespaces,
events: [],
accounts: Account.stubSet(),
acknowledged: acknowledged,
Expand Down
54 changes: 37 additions & 17 deletions Tests/WalletConnectSignTests/WCSessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,32 +141,40 @@ final class WCSessionTests: XCTestCase {

// MARK: Namespace Update Tests

private func stubRequiredNamespaces() -> [String: SessionNamespace] {
private func stubRequiredNamespaces() -> [String: ProposalNamespace] {
return [
"eip155": SessionNamespace(
accounts: [ethAccount, polyAccount],
"eip155": ProposalNamespace(
chains: [ethAccount.blockchain, polyAccount.blockchain],
methods: ["method", "method-2"],
events: ["event", "event-2"],
extensions: nil)]
}

private func stubRequiredNamespacesWithExtension() -> [String: SessionNamespace] {
private func stubCompliantNamespaces() -> [String: SessionNamespace] {
return [
"eip155": SessionNamespace(
accounts: [ethAccount, polyAccount],
methods: ["method", "method-2"],
events: ["event", "event-2"],
extensions: nil)]
}

private func stubRequiredNamespacesWithExtension() -> [String: ProposalNamespace] {
return [
"eip155": ProposalNamespace(
chains: [ethAccount.blockchain, polyAccount.blockchain],
methods: ["method", "method-2"],
events: ["event", "event-2"],
extensions: [
SessionNamespace.Extension(
accounts: [ethAccount, polyAccount],
ProposalNamespace.Extension(
chains: [ethAccount.blockchain, polyAccount.blockchain],
methods: ["method-2", "newMethod-2"],
events: ["event-2", "newEvent-2"])])]
}

func testUpdateEqualNamespaces() {
let namespace = stubRequiredNamespaces()
var session = WCSession.stub(namespaces: namespace)
XCTAssertNoThrow(try session.updateNamespaces(namespace))
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces())
XCTAssertNoThrow(try session.updateNamespaces(stubCompliantNamespaces()))
}

func testUpdateNamespacesOverRequirement() {
Expand Down Expand Up @@ -195,18 +203,30 @@ final class WCSessionTests: XCTestCase {
}

func testUpdateLessThanRequiredChains() {
var session = WCSession.stub(namespaces: stubRequiredNamespaces())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces())
XCTAssertThrowsError(try session.updateNamespaces([:]))
}

func testUpdateReplaceAccount() {
let newEthAccount = Account("eip155:1:0xab16a96d359ec26a11e2c2b3d8f8b8942d5bfcdf")!
let valid = [
"eip155": SessionNamespace(
accounts: [newEthAccount, polyAccount],
methods: ["method", "method-2"],
events: ["event", "event-2"],
extensions: nil)]
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces())
XCTAssertNoThrow(try session.updateNamespaces(valid))
}

func testUpdateLessThanRequiredAccounts() {
let invalid = [
"eip155": SessionNamespace(
accounts: [ethAccount],
methods: ["method", "method-2"],
events: ["event", "event-2"],
extensions: nil)]
var session = WCSession.stub(namespaces: stubRequiredNamespaces())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}

Expand All @@ -217,7 +237,7 @@ final class WCSessionTests: XCTestCase {
methods: ["method"],
events: ["event", "event-2"],
extensions: nil)]
var session = WCSession.stub(namespaces: stubRequiredNamespaces())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}

Expand All @@ -228,7 +248,7 @@ final class WCSessionTests: XCTestCase {
methods: ["method", "method-2"],
events: ["event"],
extensions: nil)]
var session = WCSession.stub(namespaces: stubRequiredNamespaces())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespaces())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}

Expand All @@ -239,7 +259,7 @@ final class WCSessionTests: XCTestCase {
methods: ["method", "method-2"],
events: ["event", "event-2"],
extensions: nil)]
var session = WCSession.stub(namespaces: stubRequiredNamespacesWithExtension())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}

Expand All @@ -254,7 +274,7 @@ final class WCSessionTests: XCTestCase {
accounts: [ethAccount],
methods: ["method-2", "newMethod-2"],
events: ["event-2", "newEvent-2"])])]
var session = WCSession.stub(namespaces: stubRequiredNamespacesWithExtension())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}

Expand All @@ -269,7 +289,7 @@ final class WCSessionTests: XCTestCase {
accounts: [ethAccount, polyAccount],
methods: ["method-2"],
events: ["event-2", "newEvent-2"])])]
var session = WCSession.stub(namespaces: stubRequiredNamespacesWithExtension())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}

Expand All @@ -284,7 +304,7 @@ final class WCSessionTests: XCTestCase {
accounts: [ethAccount, polyAccount],
methods: ["method-2", "newMethod-2"],
events: ["event-2"])])]
var session = WCSession.stub(namespaces: stubRequiredNamespacesWithExtension())
var session = WCSession.stub(requiredNamespaces: stubRequiredNamespacesWithExtension())
XCTAssertThrowsError(try session.updateNamespaces(invalid))
}
}